# MATIAS API UBL 2.1 > API REST de facturación electrónica para Colombia (DIAN, UBL 2.1): factura electrónica, POS electrónico, documento soporte, notas crédito/débito, nómina electrónica, eventos RADIAN y sector salud (Res. 000948 de 2026). Guía rápida para agentes de IA: - Base URL: en los ejemplos, `{{url}}` es la base URL de la API. Sandbox (gratuito, sin envío a la DIAN): `https://sandbox-api.matias-api.com/api/ubl2.1`. La de producción se entrega al contratar el servicio. - Autenticación: header `Authorization: Bearer {token}` (token de acceso personal), además de `Accept: application/json` y `Content-Type: application/json`. - Casas de Software: agregan `?client_uuid={uuid}` para operar en nombre de una empresa cliente. - Cada página de este índice está en Markdown plano (`.md`). También se obtiene enviando `Accept: text/markdown` a la URL HTML. - Especificación OpenAPI 3 de los endpoints públicos: https://docs.matias-api.com/openapi.json --- # Introducción Matias es una plataforma de **facturación electrónica** que permite a las empresas emitir, enviar y gestionar facturas electrónicas de forma segura y eficiente. Nuestra **API RESTful** proporciona acceso completo a los servicios de Matias. Con Matias, los desarrolladores pueden integrar la funcionalidad de facturación electrónica en sus aplicaciones, automatizando completamente el proceso de emisión de documentos de forma segura, legal y eficiente. --- ## 🛠️ Herramientas de Desarrollo Antes de leer la documentación completa, ten a mano las dos herramientas que más vas a usar durante tu integración: ### 📮 Colección Postman Importa todos los endpoints con payloads de ejemplo listos para ejecutar. La forma más rápida de probar la API sin escribir código. [🔗 Abrir Colección Postman](https://documenter.getpostman.com/view/8699065/2s9YyvBLby) Incluye ejemplos de Factura, Nota Crédito, Nómina, Salud, Eventos RADIAN y más. ### 📘 Swagger UI Explora y prueba los endpoints interactivamente desde el navegador. Requiere tu token de acceso. Disponible únicamente para clientes con servicio activo. :::tip ¿Quieres probar sin un contrato? Usa el **[Sandbox gratuito](https://docs.matias-api.com/docs/sandbox/quickstart.md)** — no requiere contrato ni configuración DIAN. Crea tu cuenta en `https://sandbox-auth.matias-api.com/` y empieza a probar en minutos. ::: --- ## 🌐 URL Base de la API :::info 🚀 Acceso a la Plataforma **Sandbox (Gratuito - Sin Contrato):** - ✅ Acceso completamente **GRATUITO** - ✅ Sin necesidad de contrato - ✅ Perfecto para pruebas y desarrollo - ✅ Mismos endpoints que producción - ✅ Datos de prueba disponibles - ✅ Crear cuenta en: `https://sandbox-auth.matias-api.com/` **Producción (Requiere Contrato):** - ✅ Acceso exclusivo para clientes con servicio activo - ✅ URL base de producción proporcionada al contratar - ✅ Credenciales obtenidas al registrarse en el ambiente de producción - ✅ Emisión de documentos reales ante DIAN - ✅ Soporte técnico especializado incluido - ✅ Certificado digital y resoluciones DIAN requeridas [📖 Ver documentación completa del Sandbox](https://docs.matias-api.com/docs/sandbox/quickstart.md) ::: En toda la documentación, utilizaremos el parámetro `{{URL}}` como marcador de posición para la URL base de producción, la cual le será proporcionada al contratar el servicio. De manera similar, usaremos `{{SANDBOX_URL}}` para referirnos al entorno de pruebas. :::info Enlaces de Referencia **Producción (`{{URL}}`)**: ```text https://api.ejemplo.com (Ejemplo ilustrativo) ``` **Sandbox (`{{SANDBOX_URL}}`)**: - **API:** `https://sandbox-api.matias-api.com/api/ubl2.1` - **Frontend Web:** `https://sandbox-auth.matias-api.com/` ::: ### ⚠️ Cuentas Separadas: Producción vs Sandbox :::warning IMPORTANTE: Credenciales Diferentes para Cada Entorno **Las credenciales de Producción NO funcionan automáticamente en Sandbox.** Debes crear **DOS CUENTAS DIFERENTES** — una para cada entorno: #### 1️⃣ Cuenta de Producción - **URL Base:** `{{URL}}` - **Portal Web:** `{{URL}}/#/auth/login` - **Propósito:** Emitir documentos reales ante la DIAN - **Requisitos:** Certificado digital, información del software DIAN, resolución de facturación #### 2️⃣ Cuenta de Sandbox (Pruebas) - **URL API:** `https://sandbox-api.matias-api.com` - **Portal Web:** `https://sandbox-auth.matias-api.com/` - **Propósito:** Probar tu integración sin enviar documentos a la DIAN - **Requisitos:** Crear una cuenta separada en el portal de sandbox - **Datos de Prueba:** Disponibles para testing sin restricciones #### 📋 Flujo Recomendado 1. **Crea tu cuenta en Producción** → Obtienes credenciales de prod 2. **Crea una cuenta separada en Sandbox** → Obtienes credenciales de sandbox 3. **Prueba tu integración en Sandbox** → Usa credenciales de sandbox 4. **Valida todo funciona correctamente** → Luego usa credenciales de prod en tu app final #### 🔄 Cambiar entre Entornos Para cambiar de Sandbox a Producción (o viceversa), simplemente: - Cambia la URL base en tu código - Usa las credenciales correspondientes al entorno **Ejemplo:** ```javascript // Sandbox const SANDBOX_URL = 'https://sandbox-api.matias-api.com'; const SANDBOX_TOKEN = 'token_de_sandbox_aqui'; // Producción const PROD_URL = '{{URL}}'; const PROD_TOKEN = 'token_de_produccion_aqui'; ``` ::: ### ⚠️ Requisitos Previos Obligatorios > [!IMPORTANT] > **Antes de usar la API para emitir documentos ante la DIAN, debe haber realizado:** > > 1. ✅ **Subida del certificado digital** (Resolución DIAN) > 2. ✅ **Información del Software** generada por portal DIAN > 3. ✅ **Resolución de facturación** (números de rango) > > Registre esta información de forma visual en el portal web: > `{{URL}}/#/auth/login` > > Sin estos requisitos previos, la API rechazará sus solicitudes de emisión de facturas. ### 📤 Formato de las Solicitudes - **Tipo de Formato:** Todas las solicitudes hacia la API deben estar en formato `JSON`. Este formato se aplica tanto para las peticiones enviadas como para las respuestas recibidas, facilitando la estandarización del intercambio de datos. - **Uso de HTTPS:** Es obligatorio realizar solicitudes a través de HTTPS, dado que la API no procesará peticiones enviadas mediante HTTP. Esta medida garantiza la seguridad y la integridad de los datos transmitidos. --- ## 🔐 Flujo de Autenticación {#flujo-de-autenticación} :::tip Nuevo en v3.0.0: Dos Métodos de Autenticación Ofrecemos **dos opciones** para autenticarte según las necesidades de tu integración: - **Personal Access Tokens (PAT):** Recomendado para integraciones servidor-a-servidor, scripts automatizados y tareas en segundo plano. - **OAuth2 Tradicional (login):** Para aplicaciones web con usuarios que inician sesión interactivamente. ::: **🗺️ Ver Diagrama: Flujo OAuth2 Tradicional (login)** ``` ┌─────────────────────────────────────────────────────────────┐ │ FLUJO OAUTH2 TRADICIONAL (v2.x) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. REGISTRO 2. OBTENER TOKEN │ │ POST /register POST /auth/login │ │ ↓ ↓ │ │ [Email/Password] [Credenciales] │ │ ↓ ↓ │ │ Confirmación Email access_token (90 días) │ │ (OBLIGATORIA: enlace ↓ │ │ del correo; sin ella │ │ el login responde 404) │ │ ──────────────────────────────────────────────────────── │ │ │ │ 3. USAR TOKEN 4. REVOCACIÓN │ │ Bearer {token} GET /auth/logout │ │ ↓ ↓ │ │ [Solicitudes API] Token Revocado │ │ ↓ │ │ Respuestas JSON │ │ │ └─────────────────────────────────────────────────────────────┘ ``` **🗺️ Ver Diagrama: Flujo Personal Access Tokens (PAT)** ``` ┌─────────────────────────────────────────────────────────────┐ │ FLUJO PERSONAL ACCESS TOKENS (v3.0.0) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. LOGIN INICIAL 2. CREAR PAT │ │ POST /auth/login POST /tokens │ │ ↓ ↓ │ │ [Obtener token inicial] [name, expires_in_days] │ │ ↓ ↓ │ │ Token temporal (90 días) Personal Token (1-365 días) │ │ ↓ │ │ ──────────────────────────────────────────────────────── │ │ │ │ 3. USAR PAT 4. GESTIÓN │ │ Bearer {pat_token} GET /tokens (listar) │ │ ↓ GET /tokens/{id} │ │ [Solicitudes API] DELETE /tokens/{id} │ │ ↓ POST /tokens/revoke-all │ │ Respuestas JSON ↓ │ │ Token específico revocado │ │ │ │ VENTAJAS PAT: │ │ ✅ Expiración configurable (1-365 días, 90 por defecto) │ │ ✅ Múltiples tokens por cuenta │ │ ✅ Revocación selectiva instantánea │ │ ✅ Autogestión completa (sin contactar soporte) │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 🛡️ Autenticación de API con OAuth2 {#autenticación-de-api-con-oauth2} La autenticación para acceder a la API se gestiona mediante **Tokens de acceso** siguiendo el estándar de autenticación OAuth2. Esto asegura que las interacciones con la API sean seguras y estén autorizadas. :::info Ventajas de Personal Access Tokens (PAT) - **Creación self-service:** Tú mismo los creas y los gestionas directamente. - **Expiración configurable:** Desde 1 hasta 365 días; si no se indica, 90 días (recomendado: 30-60 días). - **Límite de creación:** Hasta 10 tokens nuevos por día y usuario. - **Revocación instantánea:** Revoca cualquier token de forma selectiva. - **Múltiples tokens:** Usa tokens únicos para diferentes servidores o entornos de integración. [📖 Ver documentación completa de Personal Access Tokens](https://docs.matias-api.com/docs/endpoints/intro-auth#personal-access-tokens) ::: ### 📝 Encabezado de Autorización {#encabezado-de-autorización} Para acceder a los recursos protegidos de la API, incluya el encabezado de autorización en cada solicitud con el siguiente formato: ```http Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM... Content-Type: application/json Accept: application/json ``` --- ## 📝 Paso 1: Registro en la API Para acceder y utilizar los servicios de la API, el primer paso es registrarse en nuestra plataforma de autenticación. ### ⚙️ Opciones de Registro 1. **A través de nuestro sitio web:** Complete el formulario de registro y verifique su correo con el enlace que recibirá. 2. **Mediante el servicio REST:** Realice una petición `POST` al endpoint público `{{URL}}/register` (sin token), con las cabeceras `Content-Type: application/json` y `Accept: application/json`. :::warning Este endpoint crea cuentas principales, no sub-cuentas `POST /register` crea una **cuenta principal** (usuario y empresa) de tipo desarrollador. Si usted es una **Casa de Software** y quiere crear la cuenta de un cliente, use [`POST /company/{uuid}/customer`](https://docs.matias-api.com/docs/endpoints/company#clientes-casa-software) con su token. Enviar `company_id` a `/register` responde 422. ::: **Campos obligatorios:** | Parámetro | Tipo | Requerido | Descripción | Ejemplo | |-----------|------|-----------|-------------|----------| | `first_name` | `string` | ✅ Sí | Nombre del usuario (máx. 100) | `"Juan"` | | `last_name` | `string` | ✅ Sí | Apellido del usuario (máx. 100) | `"Perez"` | | `company_name` | `string` | ✅ Sí | Razón social de la empresa (máx. 100) | `"Mi Empresa SAS"` | | `email` | `string` | ✅ Sí | Correo electrónico válido y con dominio existente (máx. 255). Único | `"usuario@miempresa.com"` | | `password` | `string` | ✅ Sí | Contraseña (mínimo 8 caracteres) | `"MiPassword123!"` | | `password_confirmation` | `string` | ✅ Sí | Confirmación de la contraseña | `"MiPassword123!"` | | `dni` | `string` | ✅ Sí | NIT o documento, **solo dígitos** (sin dígito de verificación, puntos ni guiones), máx. 15. Único. El DV lo calcula la API | `"900123456"` | **Campos opcionales** (si no se envían, se usa el valor por defecto): | Parámetro | Tipo | Por defecto | Descripción | |-----------|------|-------------|-------------| | `trade_name` | `string` | — | Nombre comercial (máx. 250). Se guarda tal como se envía | | `identity_document_id` | `integer` | `3` | Tipo de documento ([ver glosario](https://docs.matias-api.com/docs/glossary.md)) | | `type_organization_id` | `integer` | `1` | Tipo de organización ([ver glosario](https://docs.matias-api.com/docs/glossary.md)) | | `tax_regime_id` | `integer` | `1` | Régimen fiscal ([ver glosario](https://docs.matias-api.com/docs/glossary.md)) | | `tax_level_id` | `integer` | `5` | Nivel fiscal ([ver glosario](https://docs.matias-api.com/docs/glossary.md)) | | `country_id` | `integer` | `45` | País | | `city_id` | `integer` | `149` | Ciudad ([ver glosario](https://docs.matias-api.com/docs/glossary.md)) | | `address` | `string` | `""` | Dirección física | | `location` | `string` | `""` | Ubicación (texto libre) | | `postal_code` | `string` | `""` | Código postal | | `mobile` | `string` | `""` | Teléfono celular | | `phone` | `string` | `""` | Teléfono fijo | | `web` | `string` | `""` | Sitio web | - El usuario se crea siempre como **desarrollador**: un `type_id` enviado se ignora. - Nombres, apellidos y razón social se guardan en **mayúsculas**. - La cuenta recibe una suscripción inicial **FREE de 100 documentos por 1 año**. En el sandbox, la cuenta principal recibe en su lugar una suscripción de pruebas de **5.000 documentos por 1 año**. - La respuesta **no** incluye token: el correo queda **sin verificar** y hay que abrir el enlace del correo antes de iniciar sesión (ver [Paso 2](https://docs.matias-api.com/docs/intro#obtener-el-token-de-acceso)). **📦 Ver JSON de Petición (Request Body)** ```json { "first_name": "Juan", "last_name": "Perez", "company_name": "Mi Empresa SAS", "email": "usuario@miempresa.com", "password": "MiPassword123!", "password_confirmation": "MiPassword123!", "dni": "900123456", "address": "Calle 123 #45-67", "mobile": "3001234567", "city_id": 149, "identity_document_id": 3, "type_organization_id": 1, "tax_regime_id": 1, "tax_level_id": 5 } ``` **📦 Ver Respuesta Exitosa (HTTP 200)** ```json { "message": "Empresa creada con éxito. Verifique su dirección de correo electrónico: usuario@miempresa.com", "success": true } ``` **📦 Ver Respuestas de Error (HTTP 422 / 400 / 500)** **Respuesta HTTP 422 (Error de Validación):** formato estándar, sin campo `success`. Requiere `Accept: application/json`; sin esa cabecera, la respuesta es una redirección (HTTP 302). ```json { "message": "El correo electrónico ya existe (and 1 more error)", "errors": { "email": [ "El correo electrónico ya existe" ], "dni": [ "El NIT ya existe" ] } } ``` | Campo | Mensajes posibles | |-------|-------------------| | `first_name` | `El nombre es requerido` | | `last_name` | `El apellido es requerido` | | `company_name` | `El nombre de la empresa es requerido` | | `email` | `El correo electrónico es requerido` · `El correo electrónico no es válido o su dominio no existe` · `El correo electrónico ya existe` | | `dni` | `El NIT es requerido` · `El NIT ya existe` · `El NIT debe contener solo dígitos, sin puntos, guiones ni dígito de verificación` · `El NIT no puede tener más de 15 dígitos` | | `company_id` | `Este endpoint no crea sub-cuentas. Use POST /api/ubl2.1/company/{uuid}/customer con el token del desarrollador.` | Los errores de `password` (longitud, confirmación) usan los mensajes estándar de validación. **Respuesta HTTP 400 (Dato Inválido):** un valor de catálogo no existe (por ejemplo, `country_id`, `city_id` o `tax_level_id`). No se crea nada. El `message` trae el detalle técnico del rechazo: revise los IDs contra el [glosario](https://docs.matias-api.com/docs/glossary.md). ```json { "success": false, "message": "Detalle técnico del valor de catálogo rechazado" } ``` **Respuesta HTTP 500 (Error Interno):** ```json { "success": false, "message": "Error interno del servidor. ...", "reference": "3f1c2b9e-8d7a-4c6b-9e5f-1a2b3c4d5e6f" } ``` Indique la `reference` al soporte técnico. :::caution Si el 500 llega por el correo de verificación Si falla el envío del correo de verificación, la API responde 500 aunque **la cuenta ya quedó creada**. No repita `POST /register` (respondería 422 `El correo electrónico ya existe`): pida un correo nuevo con [`POST /email/verification-notification`](https://docs.matias-api.com/docs/endpoints/intro-auth#verificacion-correo). ::: --- ## 🔑 Paso 2: Obtener el Token de Acceso :::warning Primero verifique su correo Después de `POST /register`, abra el enlace de verificación que llega al correo registrado. Hasta hacerlo, el login responde **HTTP 404** con `El correo electrónico no ha sido verificado. Comuníquese con el administrador.` Si el correo no llegó o el enlace venció, pida uno nuevo con [`POST /email/verification-notification`](https://docs.matias-api.com/docs/endpoints/intro-auth#verificacion-correo). ::: Con el correo verificado, obtenga su token de acceso enviando una petición `POST` al endpoint `{{URL}}/auth/login`. Para emitir documentos ante la DIAN necesitará, además, los [requisitos previos](https://docs.matias-api.com/docs/intro#nota-importante) configurados en el portal. ### Parámetros del Body | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `email` | `string` | ✅ Sí | Correo electrónico registrado | | `password` | `string` | ✅ Sí | Contraseña de la cuenta | | `remember_me` | `boolean` | No | Opcional; no cambia la vigencia del token | **📦 Ver JSON de Petición (Request Body)** ```json { "email": "usuario@miempresa.com", "password": "MiPassword123!" } ``` **📦 Ver Respuesta Exitosa (HTTP 200)** ```json { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM", "token_id": "f2e1eb82d52213a6b1c0d3e4f5a60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9", "user": { "id": 1, "type_id": 2, "first_name": "JUAN", "last_name": "PEREZ", "email": "usuario@miempresa.com", "avatar": null, "active": 1, "name": "JUAN PEREZ", "avatarUrl": null, "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } }, "expires_at": "2026-05-02 19:55:42", "message": "Bienvenido. Su sesión ha sido iniciada con éxito.", "success": true } ``` El token vence en `expires_at` (por defecto, 90 días después del login). **📦 Ver Respuestas de Inicio de Sesión Rechazado (HTTP 404)** Todos los rechazos del login responden **HTTP 404** (no 401): ```json { "message": "Credenciales inválidas.", "success": false } ``` Otros mensajes posibles: `El usuario se encuentra inactivo. Comuníquese con el administrador.`, `El correo electrónico no ha sido verificado. Comuníquese con el administrador.`, `No se ha encontrado el perfil de usuario.`, `No se ha encontrado la empresa asociada al usuario.` y `La empresa se encuentra inactiva. Comuníquese con el administrador.` [Ver detalle](https://docs.matias-api.com/docs/endpoints/intro-auth#autenticacion). --- ## 🔐 Paso 3: Uso del Token de Acceso Incluya el token obtenido en todas sus llamadas HTTP a endpoints protegidos. Los ejemplos consultan el usuario autenticado con `GET {{URL}}/auth/user` (equivalente a `GET {{URL}}/profile`), que responde con el usuario dentro de `dataRecords.data`. ### 📄 Formato del Encabezado ```http Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM... ``` ### 💻 Ejemplos de Implementación **Node.js** ```javascript const axios = require('axios'); const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM...'; // Token de acceso const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${token}` }; axios.get('{{URL}}/auth/user', { headers }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` **Python** ```python import requests url = "{{URL}}/auth/user" token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM..." headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': f'Bearer {token}' } response = requests.get(url, headers=headers) print(response.json()) ``` **PHP** ```php $token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM...'; $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "{{URL}}/auth/user", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_HTTPHEADER => array( "Content-Type: application/json", "Accept: application/json", "Authorization: Bearer " . $token ), )); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Java** ```java OkHttpClient client = new OkHttpClient().newBuilder().build(); String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM..."; Request request = new Request.Builder() .url("{{URL}}/auth/user") .method("GET", null) .addHeader("Content-Type", "application/json") .addHeader("Accept", "application/json") .addHeader("Authorization", "Bearer " + token) .build(); Response response = client.newCall(request).execute(); ``` **C#** ```csharp var client = new RestClient("{{URL}}/auth/user"); var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM..."; var request = new RestRequest(Method.GET); request.AddHeader("Content-Type", "application/json"); request.AddHeader("Accept", "application/json"); request.AddHeader("Authorization", "Bearer " + token); IRestResponse response = client.Execute(request); Console.WriteLine(response.Content); ``` **Ruby** ```ruby require 'uri' require 'net/http' url = URI("{{URL}}/auth/user") token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM..." http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Get.new(url) request["Content-Type"] = "application/json" request["Accept"] = "application/json" request["Authorization"] = "Bearer " + token response = http.request(request) puts response.read_body ``` **Go** ```go package main import ( "fmt" "net/http" "io/ioutil" ) func main() { url := "{{URL}}/auth/user" token := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM..." client := &http.Client {} req, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println(err) return } req.Header.Add("Content-Type", "application/json") req.Header.Add("Accept", "application/json") req.Header.Add("Authorization", "Bearer " + token) res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body)) } ``` --- ## 🚫 Paso 4: Revocar el Token :::caution Revocación de Sesión (Logout) Para invalidar un token activo antes de que venza, envíe una petición **GET** a `{{URL}}/auth/logout` con el token en la cabecera de autorización. ::: **📦 Ver Respuestas de Revocación** **Respuesta Exitosa (HTTP 200):** ```json { "message": "Successfully logged out", "success": true } ``` **Respuesta de Error / No Autenticado (HTTP 401):** sin token, o con un token inválido o ya revocado. Este JSON (sin campo `success`) solo se recibe si la petición envía `Accept: application/json`; sin esa cabecera, la respuesta es una redirección (HTTP 302). ```json { "message": "Unauthenticated." } ``` El logout revoca solo el token usado en la petición. Para revocar un Personal Access Token concreto o todos los demás, use [`DELETE /tokens/{id}` o `POST /tokens/revoke-all`](https://docs.matias-api.com/docs/endpoints/intro-auth#personal-access-tokens). --- ## 🔗 Siguientes Pasos y Conceptos Avanzados Una vez completada la autenticación, explore los temas avanzados y recursos clave para la integración: ### 🔔 Webhooks: Notificaciones en Tiempo Real :::tip Eventos del Ciclo de Vida Los webhooks le permiten recibir notificaciones automáticas y seguras (firmadas con HMAC-SHA256) directamente en su servidor cuando ocurren eventos clave como: - **Documentos electrónicos** (los mismos eventos para facturas, notas, documento soporte, nómina y demás tipos): `document.created`, `document.emitted`, `document.accepted`, `document.rejected`, `document.voided` - **Correos:** `email.sent`, `email.delivered`, `email.bounced`, `email.complaint`, `email.opened`, `email.clicked` - **Envío masivo:** `bulk.batch.completed` Suscribirse a un evento que no está en el catálogo responde 422 (`Evento inválido: `). [📖 Ver catálogo completo de eventos](https://docs.matias-api.com/docs/endpoints/webhooks#eventos-disponibles) · [Firma HMAC](https://docs.matias-api.com/docs/endpoints/webhooks#seguridad-hmac) ::: ### 📊 Límites de Consumo y Membresías {#límites-de-consumo-y-membresías} Cada cuenta tiene una **bolsa de documentos por periodo de suscripción** (normalmente anual, ampliable con recargas), no un cupo mensual. Los correos tienen sus propios topes según el plan: un máximo anual de correos (contado por año calendario, desde el 1 de enero), un máximo de destinatarios por envío y un máximo de reenvíos por documento. Consulte su saldo con `GET /memberships/summary` o el historial de recargas con `GET /memberships/recharges`. **📦 Ver Respuestas de Límite de Consumo (HTTP 402 / 429 / 413)** **Sin cupo o membresía inactiva (HTTP 402)** — al enviar un documento: ```json { "success": false, "message": "Límite de documentos de la membresía alcanzado o membresía inactiva." } ``` **Plan FREE: límite diario alcanzado (HTTP 429)** — el plan FREE permite un número de documentos por día (10 por defecto): ```json { "success": false, "message": "Límite diario alcanzado. Plan FREE permite 10 documentos por día.", "limit": 10, "consumed_today": 10, "upgrade_url": "https://api.ejemplo.com/upgrade" } ``` **Envío masivo: cupo insuficiente para el lote (HTTP 402):** ```json { "success": false, "message": "Cuota insuficiente para procesar el lote completo.", "requested": 250 } ``` **Envío masivo: lote mayor que el máximo de su suscripción (HTTP 413):** ```json { "success": false, "message": "El lote excede el máximo permitido para tu suscripción.", "max_items": 100, "received": 250, "upgrade_url": "https://api.ejemplo.com/upgrade" } ``` Las respuestas de límite no traen un código de error ni una fecha de reinicio: use `GET /memberships/summary` para conocer el consumo y el saldo. [📖 Consultar consumo en tiempo real](https://docs.matias-api.com/docs/endpoints/memberships-health#memberships-consumption) --- ### 🏛️ Marco Regulatorio DIAN Matias cumple rigurosamente con los marcos regulatorios y anexos técnicos oficiales vigentes de la DIAN para documentos electrónicos: | Documento | Resolución | Versión | Estado | |-----------|-----------|---------|---------| | **Factura Electrónica** | 000165/2024 | v1.9 | ✅ Vigente | | **Nómina Electrónica** | 0000040/2024 | v3.0 | ✅ Vigente | | **RADIAN** | 000198/2024 | v2.0 | ✅ Vigente | | **Documento Soporte** | 000160/2024 | v1.1 | ✅ Vigente | #### 🚀 Accesos Rápidos a Guías Técnicas - 📄 **[Campos del Documento](https://docs.matias-api.com/docs/billing-fields.md)** - Especificaciones completas de campos y Sector Salud. - 💼 **[Nómina Electrónica](https://docs.matias-api.com/docs/payroll/payroll-fields.md)** - Guía completa de campos y devengos/deducciones. - 🌐 **[Eventos RADIAN](https://docs.matias-api.com/docs/endpoints/events-radian.md)** - Endpoints para acuses y recepción de facturas. - 📋 **[Documento Soporte](https://docs.matias-api.com/docs/endpoints/support-documents.md)** - Operaciones con no obligados a facturar. - 📚 **[Glosario Técnico](https://docs.matias-api.com/docs/glossary.md)** - Términos técnicos, normativas y acrónimos. - 📥 **[Anexos Oficiales DIAN](https://www.dian.gov.co/impuestos/factura-electronica)** - Descargas directas del portal DIAN. --- ## 💬 Soporte y Ayuda ¿Necesita soporte durante su proceso de integración? Nuestro equipo técnico especializado está disponible para guiarle. :::warning Canales de Atención Exclusivos El soporte técnico y de integración comercial se brinda **únicamente a clientes con suscripciones activas**. ::: :::tip Canales de Soporte Técnico ### 🎟️ Centro de Ayuda (Sistema de Tickets) Para reportar fallos, dudas o incidentes con integraciones activas, abra un ticket en: ``` https://support.lopezsoft.net.co/portal ``` **Al crear un caso, asegúrese de incluir:** - ✅ Descripción detallada del comportamiento esperado y obtenido. - ✅ Endpoint exacto utilizado y método HTTP. - ✅ Payload (JSON) completo de la petición. - ✅ JSON completo de la respuesta de error o código HTTP devuelto. ### 📧 Contacto por Email Escríbanos a: **soporte@matias.com.co** para consultas generales. ### 💚 Canal Rápido de WhatsApp Para dudas técnicas rápidas o verificación de estado de servicios: ::: --- **Versión de Documentación:** 3.7.0 | **Última actualización:** Agosto 2026 | **API:** v3.7.0 --- # 🤖 Documentación para agentes de IA Toda la documentación de MATIAS API está disponible en formatos que un asistente de IA puede leer directamente, sin navegar el sitio: Markdown plano, el estándar [llms.txt](https://llmstxt.org) y una especificación OpenAPI 3. ## Recursos | Recurso | URL | Úsalo para | |---|---|---| | Índice para IA | [`/llms.txt`](https://docs.matias-api.com/llms.txt) | Que el agente vea todas las páginas y abra solo las que necesita. | | Documentación completa | [`/llms-full.txt`](https://docs.matias-api.com/llms-full.txt) | Cargar toda la documentación de una vez en un chat o en tu proyecto. | | Cada página en Markdown | Agrega `.md` a la URL, p. ej. [`/docs/intro.md`](https://docs.matias-api.com/docs/intro.md) | Pasarle a la IA solo el tema en el que trabajas. | | Especificación OpenAPI 3 | [`/openapi.json`](https://docs.matias-api.com/openapi.json) | Generar clientes, validar payloads o dar al agente el contrato exacto de cada endpoint. | | Colección Postman Sandbox | [`/sandbox-quickstart.postman_collection.json`](https://docs.matias-api.com/sandbox-quickstart.postman_collection.json) | Probar el flujo mínimo contra el sandbox. | | Colección Postman completa | [`/postman_collection.json`](https://docs.matias-api.com/postman_collection.json) | Importar todos los endpoints en Postman. Es un archivo grande (más de 5 MB): para un chat usa mejor `llms-full.txt`. | Cada página del sitio tiene además, arriba a la derecha, los botones **Copiar Markdown**, **Ver .md**, **Abrir en ChatGPT** y **Abrir en Claude**. :::tip Negociación de contenido Un agente que pide cualquier página con el header `Accept: text/markdown` recibe la versión Markdown en lugar del HTML: ```bash curl -L -H "Accept: text/markdown" https://docs.matias-api.com/docs/intro/ ``` ::: ## Cómo usarlo con tu asistente ### ChatGPT, Claude y otros chats Pega en el chat el enlace de la página que necesitas (con `.md`) o el de `llms-full.txt`, o descarga el archivo y adjúntalo. Ejemplo de instrucción: ```text Lee https://docs.matias-api.com/llms-full.txt y ayúdame a emitir una factura electrónica contra el sandbox de MATIAS API desde mi aplicación en . ``` ### Cursor, GitHub Copilot, Claude Code y otros editores Guarda la documentación dentro de tu proyecto para que el asistente la tenga como contexto: ```bash curl -o docs/matias-api.md https://docs.matias-api.com/llms-full.txt curl -o docs/matias-api.openapi.json https://docs.matias-api.com/openapi.json ``` Si tu editor permite indexar documentación por URL, regístrala con `https://docs.matias-api.com/llms.txt`. ## Buenas prácticas :::danger Nunca compartas tu token No pegues tu token de acceso (`Authorization: Bearer ...`) ni certificados en un chat de IA. Para que el asistente pruebe llamadas, usa el [Sandbox](https://docs.matias-api.com/docs/sandbox/quickstart.md) y variables de entorno en tu código. ::: - Pide al asistente que trabaje primero contra el **sandbox** (`https://sandbox-api.matias-api.com/api/ubl2.1`) y que use los [valores mágicos](https://docs.matias-api.com/docs/sandbox/magic-values.md) para simular respuestas de la DIAN. - Indica tu rol: si eres **Casa de Software**, las llamadas en nombre de tus clientes llevan `?client_uuid={uuid}`. - Cuando la IA proponga un campo o un endpoint, verifica que exista en [Campos del documento](https://docs.matias-api.com/docs/billing-fields.md) o en `openapi.json`. --- # 🔌 Introducción y Autenticación :::info 📖 Referencia Completa de Endpoints Los endpoints son las URL que se utilizan para acceder a los recursos de la API. Cada endpoint es un punto de acceso que devuelve datos o realiza operaciones en el servidor. [📮 Ver Colección Oficial de Postman](https://documenter.getpostman.com/view/8699065/2s9YyvBLby) ::: 🟢 **Públicos** Sin autenticación 🔐 **Privados** Requieren token 📄 **Documentos** CRUD y consultas 🔔 **Webhooks** v3.0.0 ## 🎯 Flujo Completo de una Factura Electrónica ``` 1. AUTENTICACIÓN 2. CONSULTA 3. ENVÍO 4. ESTADO /auth/login /document-type /invoice /status ↓ ↓ ↓ ↓ [Token] [Tabla DIAN] [Factura JSON] [Validado] ↓ ↓ ↓ ↓ Públicos Públicos Privados Privados ``` ## 🏷️ Tipos de Endpoints | Tipo | Autenticación | Uso | Headers | | --------------- | ------------- | --------------------------- | ------------------------------- | | **🟢 Públicos** | ❌ No | Tablas DIAN, autenticación | Ninguno | | **🔐 Privados** | ✅ Sí | Documentos, estado, eventos | `Authorization: Bearer {token}` | :::warning Importante Los endpoints privados requieren el header `Authorization: Bearer {token}` en **todas** las solicitudes. ::: --- ## 🔑 Autenticación :::warning Verifica el correo antes del primer login Las cuentas principales creadas con `POST /register` quedan con el correo **sin verificar**. Hasta abrir el enlace del correo de verificación, `POST /auth/login` responde **HTTP 404** con el mensaje `El correo electrónico no ha sido verificado. Comuníquese con el administrador.` Si el correo no llegó o el enlace venció, pide uno nuevo con [`POST /email/verification-notification`](https://docs.matias-api.com/docs/endpoints/intro-auth#verificacion-correo). Las sub-cuentas que una Casa de Software crea con `POST /company/{uuid}/customer` ya quedan verificadas. ::: **POST /auth/login — Iniciar Sesión** ```http POST {{url}}/auth/login Content-Type: application/json Accept: application/json ``` **Parámetros del Body:** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `email` | `string` | ✅ Sí | Correo electrónico registrado (formato de correo válido) | | `password` | `string` | ✅ Sí | Contraseña de la cuenta | | `remember_me` | `boolean` | No | Opcional. Se acepta por compatibilidad, pero no cambia la vigencia del token | **cURL** ```bash curl -X POST "{{url}}/auth/login" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "email": "usuario@miempresa.com", "password": "MiPassword123!" }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/auth/login`, { email: 'usuario@miempresa.com', password: 'MiPassword123!' }, { headers: { Accept: 'application/json' } }); const token = response.data.access_token; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/auth/login", [ 'headers' => ['Accept' => 'application/json'], 'json' => [ 'email' => 'usuario@miempresa.com', 'password' => 'MiPassword123!', ], ]); $token = json_decode($response->getBody())->access_token; ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImYyZTFlYjgyZDUyMjEz...", "token_id": "f2e1eb82d52213a6b1c0d3e4f5a60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9", "user": { "id": 1, "type_id": 2, "first_name": "JUAN", "last_name": "PEREZ", "email": "usuario@miempresa.com", "avatar": null, "active": 1, "name": "JUAN PEREZ", "avatarUrl": null, "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } }, "expires_at": "2026-05-15 20:00:00", "message": "Bienvenido. Su sesión ha sido iniciada con éxito.", "success": true } ``` - `access_token`: el token que se envía en `Authorization: Bearer {token}`. - `token_id`: identificador del token (el mismo `id` que aparece en `GET /tokens`). - `expires_at`: fecha de vencimiento (`YYYY-MM-DD HH:MM:SS`). Por defecto, 90 días después del login. - La respuesta **no** incluye `token_type` ni roles: el tipo de usuario está en `user.type_id` y `user.user_type` (ver [`GET /profile/types`](https://docs.matias-api.com/docs/endpoints/profile#tipos-usuario)). **❌ Inicio de Sesión Rechazado (HTTP 404)** Todos los rechazos del login responden **HTTP 404** (no 401) con el mismo formato. Se comprueban en el orden de la tabla: ```json { "message": "Credenciales inválidas.", "success": false } ``` | `message` | Causa | |-----------|-------| | `Credenciales inválidas.` | El correo o la contraseña no coinciden. | | `El usuario se encuentra inactivo. Comuníquese con el administrador.` | El usuario está inactivo. | | `El correo electrónico no ha sido verificado. Comuníquese con el administrador.` | Cuenta de `POST /register` sin verificar, o correo cambiado con `PUT /profile/update-email` y aún sin verificar. Ver [verificación de correo](https://docs.matias-api.com/docs/endpoints/intro-auth#verificacion-correo). | | `No se ha encontrado el perfil de usuario.` | El usuario no está asociado a ninguna empresa. | | `No se ha encontrado la empresa asociada al usuario.` | La empresa del usuario no existe. | | `La empresa se encuentra inactiva. Comuníquese con el administrador.` | La empresa está inactiva. | **❌ Error de Validación (HTTP 422)** Falta `email` o `password`, o el correo no tiene un formato válido. Envía `Accept: application/json` para recibir el error en JSON (sin esa cabecera, la respuesta es una redirección HTTP 302). ```json { "message": "El campo email es obligatorio.", "errors": { "email": [ "El campo email es obligatorio." ] } } ``` **GET /auth/user — Obtener Usuario Actual** ```http GET {{url}}/auth/user Authorization: Bearer {token} Accept: application/json ``` Devuelve el usuario dueño del token. Es equivalente a [`GET /profile`](https://docs.matias-api.com/docs/endpoints/profile#gestion-perfil). **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "id": 1, "type_id": 2, "first_name": "JUAN", "last_name": "PEREZ", "email": "usuario@miempresa.com", "avatar": null, "active": 1, "name": "JUAN PEREZ", "avatarUrl": null, "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } } ] }, "success": true } ``` **❌ No Autenticado (HTTP 401)** Sin token, o con un token inválido, vencido o revocado. Este formato solo se recibe si la petición envía `Accept: application/json`; sin esa cabecera, la respuesta es una redirección (HTTP 302). ```json { "message": "Unauthenticated." } ``` **GET /auth/logout — Cerrar Sesión** ```http GET {{url}}/auth/logout Authorization: Bearer {token} Accept: application/json ``` Revoca el token con el que se hace la petición y cierra la sesión asociada. Los demás tokens del usuario siguen vigentes (para revocarlos, usa [`POST /tokens/revoke-all`](https://docs.matias-api.com/docs/endpoints/intro-auth#personal-access-tokens)). **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Successfully logged out", "success": true } ``` **❌ No Autenticado (HTTP 401)** Sin token, o con un token inválido o ya revocado. Requiere `Accept: application/json`; sin esa cabecera, la respuesta es una redirección (HTTP 302). ```json { "message": "Unauthenticated." } ``` --- ## 📧 Verificación de Correo :::info 🆕 Paso obligatorio para cuentas nuevas Al registrarse con `POST /register`, la API envía un correo con un **enlace de verificación firmado y con vencimiento**. Hay que abrirlo antes de iniciar sesión. Estos endpoints son públicos: no llevan token. ::: **POST /email/verification-notification — Reenviar Correo de Verificación** ```http POST {{url}}/email/verification-notification Content-Type: application/json Accept: application/json ``` Envía de nuevo el correo de verificación. Úsalo cuando el correo no llegó o cuando el enlace venció. **Parámetros del Body:** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `email` | `string` | ✅ Sí | Correo de la cuenta pendiente de verificar | **cURL** ```bash curl -X POST "{{url}}/email/verification-notification" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"email": "usuario@miempresa.com"}' ``` **JavaScript (Axios)** ```js await axios.post(`${url}/email/verification-notification`, { email: 'usuario@miempresa.com' }, { headers: { Accept: 'application/json' } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/email/verification-notification", [ 'headers' => ['Accept' => 'application/json'], 'json' => ['email' => 'usuario@miempresa.com'], ]); ``` **✅ Correo Enviado (HTTP 200)** ```json { "message": "Se ha enviado un correo electrónico de verificación" } ``` **❌ Correo no Registrado (HTTP 404)** También se recibe si no se envía `email`. ```json { "message": "El usuario no existe.", "success": false } ``` **❌ Correo ya Verificado (HTTP 400)** ```json { "message": "El correo electrónico ya fue verificado.", "success": false } ``` **GET Enlace de verificación — Confirmar el Correo** El correo contiene un enlace con esta forma. Lo genera la API: no se construye a mano. ```http GET {{url}}/verify-email/{id}/{hash}?expires={timestamp}&signature={firma} ``` | Resultado | Comportamiento | |-----------|----------------| | Enlace válido | Marca el correo como verificado, activa el usuario y redirige al portal web, a la pantalla de inicio de sesión. Si el correo ya estaba verificado, también redirige al login. | | Enlace vencido o alterado | **HTTP 403** (`Invalid signature.`). Pide un enlace nuevo con `POST /email/verification-notification`. | | Usuario inexistente | **HTTP 404**. | Se admiten hasta 6 intentos por minuto sobre este enlace. --- ## 🔁 Recuperar Contraseña Endpoints públicos (sin token) para restablecer la contraseña por correo. **POST /forgot-password — Solicitar Enlace de Restablecimiento** ```http POST {{url}}/forgot-password Content-Type: application/json Accept: application/json ``` **Parámetros del Body:** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `email` | `string` | ✅ Sí | Correo de la cuenta (formato válido) | Envía al correo un enlace con el `token` de restablecimiento. El enlace **vence a los 60 minutos**. ```bash curl -X POST "{{url}}/forgot-password" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"email": "usuario@miempresa.com"}' ``` **✅ Enlace Enviado (HTTP 200)** ```json { "success": true, "message": "¡Recordatorio de contraseña enviado!" } ``` El correo contiene el enlace con el `token` que se usa en `POST /reset-password`. **❌ No se Pudo Enviar (HTTP 422)** El correo no está registrado o ya se pidió un enlace hace muy poco (en ese caso, `Por favor espere antes de volver a intentarlo.`). Si falta `email` o no tiene formato válido, el error es el de validación (`El campo email es obligatorio.`). ```json { "message": "No se ha encontrado un usuario con esa dirección de correo.", "errors": { "email": [ "No se ha encontrado un usuario con esa dirección de correo." ] } } ``` **POST /reset-password — Restablecer Contraseña** ```http POST {{url}}/reset-password Content-Type: application/json Accept: application/json ``` **Parámetros del Body:** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `token` | `string` | ✅ Sí | Token recibido en el enlace del correo | | `email` | `string` | ✅ Sí | Correo de la cuenta | | `password` | `string` | ✅ Sí | Nueva contraseña (mínimo 8 caracteres) | | `password_confirmation` | `string` | ✅ Sí | Debe coincidir con `password` | ```bash curl -X POST "{{url}}/reset-password" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "token": "token-recibido-en-el-correo", "email": "usuario@miempresa.com", "password": "NuevaPassword123!", "password_confirmation": "NuevaPassword123!" }' ``` **✅ Contraseña Restablecida (HTTP 200)** ```json { "success": true, "message": "¡Su contraseña ha sido restablecida!" } ``` **❌ Token Inválido o Vencido (HTTP 422)** El token no corresponde al correo enviado o ya venció (60 minutos). Si el correo no está registrado, el mensaje es `No se ha encontrado un usuario con esa dirección de correo.` Los errores de la contraseña (longitud, confirmación) llegan en `errors.password`. ```json { "message": "Este token de restablecimiento de contraseña es inválido.", "errors": { "email": [ "Este token de restablecimiento de contraseña es inválido." ] } } ``` --- ## 🔐 Gestión de Tokens (Personal Access Tokens) :::tip PAT vs OAuth2 — ¿Cuándo usar cada uno? | | **PAT** | **OAuth2 (login)** | |---|---|---| | **Caso de uso** | Integraciones servidor-a-servidor, scripts automatizados, ERP | Apps web con usuarios que inician sesión | | **Creación** | Self-service desde la API | Login con email/contraseña | | **Expiración** | Configurable: 1–365 días (por defecto, 90) | 90 días | | **Revocación** | Selectiva por ID | Logout del token usado | | **Recomendado para** | ✅ Integraciones B2B | ✅ Portales web | ::: :::info Parámetro Multi-Tenant: `client_uuid` Todos los endpoints de `/tokens` aceptan `?client_uuid={{client_uuid}}`. Una **Casa de Software** lo usa para gestionar los tokens del usuario de una de sus sub-cuentas (con relación activa). Sin el parámetro, se gestionan los tokens del propio usuario. ::: **GET /tokens — Listar Tokens** ```http GET {{url}}/tokens?client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` Obtiene la lista de los tokens **vigentes** del usuario (no incluye los revocados ni los vencidos), del más reciente al más antiguo. También aparecen los tokens de las sesiones abiertas con `POST /auth/login`, con `name` igual a `Login - {correo}`. **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "id": "cb6282ed9f5cb116416928518f9dd93b53aafc2c0273722bbe5eb7eb96d7ccfac10178f43b8fe617", "name": "Token ERP", "description": "Token de integración para facturación", "ip_address": "203.0.113.10", "user_agent": "curl/8.4.0", "scopes": [], "revoked": false, "expires_at": "2026-05-02 12:00:00", "created_at": "2026-02-01 12:00:00", "last_used_at": "2026-02-10 08:30:00", "is_expired": false } ], "total": 1 }, "success": true } ``` **GET /tokens/{token_id} — Obtener Token por ID** ```http GET {{url}}/tokens/{token_id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` | Parámetro | Ubicación | Descripción | |-----------|-----------|-------------| | `token_id` | path | ID del token (`id` en `GET /tokens`, `token_id` al crearlo) | | `client_uuid` | query | Opcional. UUID de la sub-cuenta (Casa de Software) | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "id": "cb6282ed9f5cb116416928518f9dd93b53aafc2c0273722bbe5eb7eb96d7ccfac10178f43b8fe617", "name": "Token ERP", "description": "Token de integración para facturación", "ip_address": "203.0.113.10", "user_agent": "curl/8.4.0", "scopes": [], "revoked": false, "expires_at": "2026-05-02 12:00:00", "created_at": "2026-02-01 12:00:00", "last_used_at": "2026-02-10 08:30:00", "is_expired": false } }, "success": true } ``` **❌ Token no Encontrado (HTTP 404)** El token no existe o ya fue revocado. ```json { "success": false, "message": "Token not found." } ``` **❌ Token de Otro Usuario (HTTP 403)** ```json { "success": false, "message": "Token does not belong to the authenticated user" } ``` **POST /tokens — Crear Nuevo Token** ```http POST {{url}}/tokens?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json Accept: application/json ``` **Parámetros del Body:** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `name` | `string` | ✅ Sí | Nombre descriptivo (máx. 100). Solo letras sin tilde, números, espacios, guiones (`-`), guiones bajos (`_`) y puntos | | `description` | `string` | No | Descripción del uso del token (máx. 500) | | `expires_in_days` | `integer` | No | Días de vigencia, de 1 a 365. Por defecto, 90 | :::caution Límite diario Cada usuario puede crear hasta **10 tokens por día**. Al superarlo, la API responde **HTTP 429**. ::: **cURL** ```bash curl -X POST "{{url}}/tokens" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "name": "Token Produccion", "description": "Token para integración ERP", "expires_in_days": 60 }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/tokens`, { name: 'Token Produccion', description: 'Token para integración ERP', expires_in_days: 60 }, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); // Guardar: response.data.dataRecords.data.token (solo se muestra una vez) const pat = response.data.dataRecords.data.token; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/tokens", [ 'headers' => [ 'Authorization' => "Bearer {$token}", 'Accept' => 'application/json', ], 'json' => [ 'name' => 'Token Produccion', 'description' => 'Token para integración ERP', 'expires_in_days' => 60, ], ]); // Guardar: dataRecords.data.token (solo se muestra una vez) $pat = json_decode($response->getBody())->dataRecords->data->token; ``` **✅ Respuesta Exitosa (HTTP 201)** ```json { "dataRecords": { "data": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI...", "token_id": "cb6282ed9f5cb116416928518f9dd93b53aafc2c0273722bbe5eb7eb96d7ccfac10178f43b8fe617", "name": "Token Produccion", "description": "Token para integración ERP", "expires_at": "2026-05-02 12:00:00", "created_at": "2026-03-03 12:00:00" } }, "message": "Token creado exitosamente. Asegúrese de guardar el token, ya que no podrá verlo nuevamente.", "success": true } ``` :::caution Guarda el token ahora El valor `dataRecords.data.token` solo se muestra **una vez** al crearlo. No se puede recuperar después. ::: **❌ Límite Diario Alcanzado (HTTP 429)** ```json { "success": false, "message": "Límite de creación de tokens alcanzado. Máximo 10 por día.", "error": "Rate limit exceeded", "retry_after": 3600 } ``` `retry_after` indica los segundos que faltan para poder crear otro token. **❌ Error de Validación (HTTP 422)** ```json { "message": "The token name may only contain letters, numbers, spaces, hyphens, underscores, and periods.", "errors": { "name": [ "The token name may only contain letters, numbers, spaces, hyphens, underscores, and periods." ] } } ``` **DELETE /tokens/{token_id} — Revocar Token** ```http DELETE {{url}}/tokens/{token_id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` | Parámetro | Ubicación | Descripción | |-----------|-----------|-------------| | `token_id` | path | ID del token a revocar | | `client_uuid` | query | Opcional. UUID de la sub-cuenta (Casa de Software) | | `reason` | body | Opcional. Motivo de la revocación | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Token revoked successfully", "success": true } ``` Revocar un token que ya estaba revocado también responde `200`. **❌ Token no Encontrado o de Otro Usuario (HTTP 404 / 403)** ```json { "success": false, "message": "Token not found: cb6282ed9f5cb116416928518f9dd93b53aafc2c0273722bbe5eb7eb96d7ccfac10178f43b8fe617" } ``` Si el token pertenece a otro usuario, la respuesta es **HTTP 403** con `Token does not belong to user`. **POST /tokens/revoke-all — Revocar Todos los Tokens** ```http POST {{url}}/tokens/revoke-all?client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` Revoca todos tus tokens **excepto el que estás usando actualmente**. **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "revoked_count": 0 } }, "message": "Tokens revoked successfully", "success": true } ``` --- # 🟢 Endpoints Públicos, Tablas DIAN y Funciones Auxiliares :::tip Sin autenticación — Acceso libre Estos endpoints obtienen tablas paramétricas DIAN, catálogos del sistema y funciones auxiliares de cálculo. **No requieren token** ni parámetro `client_uuid`. ::: ### Respuesta Estándar de Catálogos Todas las respuestas de consulta de catálogos siguen este patrón: ```json { "dataRecords": { "data": [ { "id": 1, "code": "01", "name": "Factura de Venta" } ] }, "success": true } ``` --- ## 📋 Tablas Referenciadas DIAN **¿Cuándo usar?** Para obtener códigos válidos antes de construir y enviar un documento electrónico. **📄 Documentos Electrónicos** | Endpoint | Uso | Ejemplo de Respuesta | |----------|-----|---------------------| | `GET /destination-environment` | Ambiente de Destino | Producción, Pruebas | | `GET /document-type` | Tipos de Documentos | `01`=Factura, `02`=Exportación, etc. | | `GET /payment-methods` | Métodos de Pago | Formas de pago válidas | | `GET /payment-means` | Medios de Pago | Efectivo, Tarjeta, etc. | | `GET /identity-documents` | Identidades | CC, NIT, Pasaporte, etc. | | `GET /organization-type` | Tipo de Organización | `1`=Persona Jurídica, `2`=Natural | | `GET /fiscal-regime` | Régimen Fiscal | Responsabilidades fiscales | | `GET /accounting-regime` | Régimen Contable | Códigos contables | | `GET /delivery-conditions` | INCOTERMS | Términos de entrega | | `GET /correction-notes` | Motivos de corrección | Notas crédito/débito | | `GET /discount-codes` | Códigos de descuento | Tipos de descuento | | `GET /operation-type` | Tipo de operación | Nacional, Exportación | | `GET /taxes` | Tributos e impuestos | IVA, ICA, INC, etc. | | `GET /quantity-units` | Unidades de cantidad | Kg, Lt, Pz, etc. | | `GET /type-item-identifications` | Identificación de Ítem | Estándar del contribuyente | | `GET /reference-price` | Unidad de referencia | Precios referenciales | | `GET /cities` | Ciudades | Parámetro opcional: `?code=` | | `GET /departments` | Departamentos | Departamentos de Colombia | | `GET /countries` | Países | Países | | `GET /currencies` | Monedas | Monedas | **🏥 Sector Salud** | Endpoint | Uso | |----------|-----| | `GET /health/user-type` | Tipo de usuario | | `GET /health/contracting` | Modalidad de contratación | | `GET /health/coverage` | Cobertura o plan de beneficios | **💼 Nómina Electrónica** | Endpoint | Uso | |----------|-----| | `GET /ep/adjustment-note-type` | Tipo de ajuste a la nota de ajuste | | `GET /ep/contract-type` | Tipo de contrato | | `GET /ep/disability-type` | Tipo de discapacidad | | `GET /ep/extra-hours` | Horas extras | | `GET /ep/payroll-period` | Periodicidad de la nómina | | `GET /ep/worker-type` | Tipo de trabajo | | `GET /ep/worker-subtype` | Subtipo de trabajo | --- ## 🛠️ Funciones Auxiliares Públicas **GET /numbers-to-letters — Convertir Números a Letras** ```http GET {{url}}/numbers-to-letters?number={number}&money={money}&money2={money2} GET {{url}}/numbersToLetters/{numero} ``` **Descripción:** Convierte una cifra numérica a su representación textual en letras (útil para la leyenda monetaria en representaciones gráficas de facturas). **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `number` / `numero` | query / path | ✅ Sí | Número o importe a convertir (ej. `58547125`). | | `money` | query | No | Denominación en singular (ej. `PESO`, `DÓLAR`). | | `money2` | query | No | Denominación de fracción (ej. `CENTAVO`). | **cURL** ```bash curl "{{url}}/numbers-to-letters?number=58547125&money=PESO&money2=CENTAVO" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/numbers-to-letters`, { params: { number: 58547125, money: 'PESO', money2: 'CENTAVO' } }); const text = response.data.letters; ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/numbers-to-letters", [ 'query' => ['number' => 58547125, 'money' => 'PESO', 'money2' => 'CENTAVO'], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "letters": "CINCUENTA Y OCHO MILLONES QUINIENTOS CUARENTA Y SIETE MIL CIENTO VEINTICINCO PESOS M/CTE", "success": true } ``` **GET /digit-verification — Calcular Dígito de Verificación (DV)** ```http GET {{url}}/digit-verification?Number={Number} GET {{url}}/dv/{nit} ``` **Descripción:** Calcula el dígito de verificación oficial según el algoritmo módulo 11 de la DIAN. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `Number` / `nit` | query / path | ✅ Sí | NIT sin dígito de verificación ni guiones (ej. `900123456`). | **cURL** ```bash curl "{{url}}/digit-verification?Number=900123456" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/digit-verification`, { params: { Number: '900123456' } }); const dv = response.data.digit; // 7 ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/digit-verification", [ 'query' => ['Number' => '900123456'], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "digit": 7, "nit": "900123456-7" } ``` --- # 📄 Facturación y Documentos Equivalentes (POS) :::info Autenticación Requerida Incluir en todos los endpoints: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Todos los endpoints de esta sección aceptan el parámetro opcional `?client_uuid={{client_uuid}}` en la query string. Permite procesar documentos en nombre de empresas cliente cuando operas como **Casa de Software**. Obtén el listado de tus clientes y sus UUIDs en: ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: ## 📋 Tipos de Documento Soportados | `type_document_id` | Tipo de Documento | Endpoint | |---|---|---| | `1` | Factura Electrónica de Venta | `POST /invoice` | | `4` | Nota Débito | `POST /notes/debit` | | `5` | Nota Crédito | `POST /notes/credit` | | `7` | Factura Sector Salud (FEV) | `POST /invoice` | | `20` | Documento Equivalente POS | `POST /invoice` | | `25` | Boleta de Ingreso a Espectáculo Público (Cine) | `POST /invoice` | | `60` | Servicios Públicos Domiciliarios (SPD) | `POST /invoice` | --- ## 1. Emisión de Facturas **POST /invoice — Enviar Factura Electrónica** ```http POST {{url}}/invoice?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Tipos soportados principales:** - Factura nacional (01) - Factura de exportación (02) - Factura contingencia (03, 04) **Ejemplos de Casos Soportados:** - Factura Básica, Decimales, Ajustes. - Factura Sector Salud, Mandatos, Compra y Venta de Divisas. - Factura con Propina, Obsequio (Regalos), Descuentos, Retenciones y Cargos. - Facturas en Moneda Extranjera (Euro, USD) y Exportación. - Factura con Impuestos (Licores AD VALOREM / ICL, ICUI, Bolsas, Varios impuestos). **Body:** JSON con estructura completa. Ver [Campos del Documento](https://docs.matias-api.com/docs/billing-fields.md) para todos los detalles técnicos. 📦 [Ver ejemplos de Factura completos](https://docs.matias-api.com/docs/jsons-billing/invoices) **cURL** ```bash curl -X POST "{{url}}/invoice" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "836", "operation_type_id": 1, "type_document_id": 1, "payments": [{"payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00"}], "customer": { "company_name": "Cliente Ejemplo", "dni": "1063279307", "email": "cliente@correo.com" }, "lines": [{"invoiced_quantity": "1", "line_extension_amount": "200.00", "description": "Producto", "price_amount": "200.00", "base_quantity": "1"}], "legal_monetary_totals": {"line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": "224.00"} }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/invoice`, payload, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }); const cufe = response.data.XmlDocumentKey; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/invoice", [ 'headers' => [ 'Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json', ], 'json' => $payload, ]); $cufe = json_decode($response->getBody())->XmlDocumentKey; ``` :::tip Cómo leer la respuesta El HTTP `200` con `success: true` indica que **la DIAN respondió**, no que haya aceptado el documento. El resultado fiscal está en `response.IsValid` y `response.StatusCode`: el documento es válido solo con `IsValid: "true"` y `StatusCode: "00"`. Un rechazo de la DIAN también llega con HTTP `200` (ver abajo). El HTTP `422` se reserva para errores de validación del JSON enviado. ::: **✅ Respuesta Exitosa — Producción (HTTP 200)** ```json { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc", "response": { "ErrorMessage": {}, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura Electrónica LZT836, ha sido autorizada.", "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc" }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500000041.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000041.xml", "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4..." }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=ad20e8c2...", "url": "https://sandbox-api.matias-api.com/qr/1/QR-0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.png", "path": "1/QR-0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.png", "data": "iVBORw0KGgoAAAANSUhEUgAA..." }, "pdf": null, "warnings": [], "success": true } ``` | Campo | Descripción | |---|---| | `uuid` | Identificador del **envío** en la plataforma. Úsalo para consultar el documento o reintentar. Es distinto del `uuid` opcional que tú puedes enviar en el body (ver [Campos del Documento](https://docs.matias-api.com/docs/billing-fields#uuid)). | | `message` | En producción es `"Solicitud procesada por la DIAN."` tanto si la DIAN acepta como si rechaza. Cambia cuando se devuelve un documento que la DIAN ya había autorizado o aplica la regla 90 (ver [Advertencias](https://docs.matias-api.com/docs/endpoints/invoices-pos#warnings-emision)). | | `XmlDocumentKey` | CUFE/CUDE del documento. | | `response` | Respuesta de la DIAN: `IsValid`, `StatusCode`, `StatusDescription`, `StatusMessage`, `ErrorMessage`. | | `AttachedDocument`, `qr`, `pdf` | Archivos generados después de la aceptación. `pdf` es `null` si no pediste `graphic_representation` ni `send_email`. En un rechazo son `null`. | | `warnings` | Arreglo de objetos `code` + `message`. Vacío si todo salió bien. Un warning **nunca** significa reenviar. Ver [Advertencias](https://docs.matias-api.com/docs/endpoints/invoices-pos#warnings-emision). | **⚠️ Rechazo DIAN — Producción (HTTP 200, `IsValid: "false"`)** La DIAN respondió, pero rechazó el documento. La petición fue exitosa (`success: true`); corrige el JSON y vuelve a enviarlo con el **mismo número**. ```json { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc", "response": { "IsValid": "false", "StatusCode": "99", "StatusDescription": "Documento con errores en campos obligatorios.", "ErrorMessage": { "string": [ "CAU04b: El campo TaxAmount es obligatorio cuando ExistsWithholdingTax es verdadero." ] }, "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc" }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": null, "qr": null, "pdf": null, "warnings": [], "success": true } ``` **🧪 Respuesta en Habilitación (software en pruebas)** Mientras el software DIAN está en ambiente de habilitación, el documento se envía al set de pruebas de forma asíncrona y la respuesta tiene otra forma: no incluye `uuid`, `message`, `warnings` ni archivos generados. Consulta el resultado con el `ZipKey` en [`POST /status/zip/{trackId}`](https://docs.matias-api.com/docs/endpoints/queries-status#consulta-estados). ```json { "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc", "ZipKey": "c2a7e8d1-4b3f-4e9a-9d2c-1f0b6a5e7d33", "response": { "ErrorMessageList": {}, "ZipKey": "c2a7e8d1-4b3f-4e9a-9d2c-1f0b6a5e7d33" }, "XmlDocumentName": "fv09010914030002500000041.xml", "ShippingMethod": "SendTestSetAsync", "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "success": true } ``` Para los errores propios de la emisión (`409`, `503`, documento posiblemente enviado) consulta [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). --- ## 2. Notas de Crédito y Débito **POST /notes/credit — Enviar Nota Crédito** ```http POST {{url}}/notes/credit?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Casos:** Devoluciones, Descuentos globales, Correcciones hacia abajo. **Campo Clave:** `type_document_id: 5` 📦 [Ver ejemplos de Nota Crédito](https://docs.matias-api.com/docs/jsons-billing/credit-note.md) **✅ Respuesta Exitosa — DIAN 200 OK** ```json { "uuid": "5c1f0a2e-7dd1-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc", "response": { "ErrorMessage": {}, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Nota Crédito NC50, ha sido autorizada." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500000050.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000050.xml", "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4..." }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=ad20e8c2...", "url": "https://sandbox-api.matias-api.com/qr/1/QR-5c1f0a2e-7dd1-11f0-ba9f-f02f74cac485.png", "path": "1/QR-5c1f0a2e-7dd1-11f0-ba9f-f02f74cac485.png", "data": "iVBORw0KGgoAAAANSUhEUgAA..." }, "pdf": null, "warnings": [], "success": true } ``` La respuesta, el rechazo DIAN y los errores siguen las mismas reglas que `POST /invoice`. Ver [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). **POST /notes/debit — Enviar Nota Débito** ```http POST {{url}}/notes/debit?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Casos:** Intereses, Cargos adicionales, Correcciones hacia arriba. **Campo Clave:** `type_document_id: 4` --- ## 3. Documentos Equivalentes (POS y Otros) **POST /invoice — POS Electrónico (Documento 20)** ```http POST {{url}}/invoice?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Casos:** - POS con cliente (consumidor final u otros). - POS sin envío de email. **Campo Clave:** `type_document_id: 20` 📦 [Ver ejemplos de POS](https://docs.matias-api.com/docs/jsons-pos/pos.md) **POST /notes/credit | /notes/debit — Notas para POS** ```http POST {{url}}/notes/credit?client_uuid={{client_uuid}} POST {{url}}/notes/debit?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Notas de crédito o débito asociadas a un documento P.O.S. **POST /invoice — Boleta de Ingreso a Cine (Documento 25)** ```http POST {{url}}/invoice?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Documento equivalente para ingreso a cine (Documento 25). **Campo Clave:** `type_document_id: 25` **POST /invoice — SPD — Servicios Públicos Domiciliarios (Documento 60)** ```http POST {{url}}/invoice?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Factura o documento equivalente por servicios públicos domiciliarios. **Campo Clave:** `type_document_id: 60` --- ## 4. Respuestas, advertencias y errores de emisión Las reglas de esta sección aplican a `POST /invoice`, `POST /notes/credit`, `POST /notes/debit`, `POST /ds/document`, `POST /ds/adjustment-note` y a los endpoints de [Numeración Automática](https://docs.matias-api.com/docs/endpoints/autoincrement.md) (`POST` y `PATCH /auto-increment/...`). :::caution No reemitas a ciegas Ante un `409`, un `503`, un error con `code: "DOCUMENT_POSSIBLY_SENT"` o una petición sin respuesta (timeout), **no generes un número nuevo**: el documento pudo llegar a la DIAN. Consulta su estado con [`GET /status`](https://docs.matias-api.com/docs/endpoints/queries-status#consulta-estados) o `GET /documents/{uuid}` y, si hace falta, reintenta en unos minutos con el **mismo número**. ::: ### 🆕 Advertencias (`warnings`) En producción toda respuesta de emisión incluye `warnings`: un arreglo de objetos con `code` y `message`. Si la DIAN ya validó el documento y falla un paso posterior (AttachedDocument, PDF, correo), la respuesta **sigue siendo HTTP 200 de éxito** y el fallo se informa aquí. La respuesta de habilitación no incluye `warnings`. | `code` | Cuándo ocurre | ¿Reenviar? | |---|---|---| | `ATTACHED_DOCUMENT_NOT_GENERATED` | La DIAN validó el documento, pero no se pudo generar el AttachedDocument (ni enviar el correo, si se pidió `send_email`). | ❌ No | | `PDF_NOT_GENERATED` | La DIAN validó el documento, pero no se pudo generar la representación gráfica. | ❌ No | | `EMAIL_NOT_SENT` | La DIAN validó el documento, pero no se pudo enviar el correo. | ❌ No | | `ATTACHMENTS_NOT_LINKED` | El documento fue procesado, pero no se pudieron vincular sus adjuntos (`attachments`). | ❌ No, si el documento es válido | | `ALREADY_AUTHORIZED_RECOVERED` | Solo producción. El número ya estaba autorizado por la DIAN y se devuelve ese documento (ver [Regla 90](https://docs.matias-api.com/docs/endpoints/invoices-pos#regla-90) y [Reintento de un número ya transmitido](https://docs.matias-api.com/docs/endpoints/invoices-pos#reintento-transmitido)). No descuenta documentos de tu plan (ver [Documento recuperado: sin cobro de consumo](https://docs.matias-api.com/docs/endpoints/invoices-pos#recuperado-sin-consumo)). | ❌ No | | `ALREADY_AUTHORIZED_OTHER_CONTENT` | Solo producción, regla 90. La DIAN ya autorizó ese número con **otro contenido**; el documento enviado no se marcó como válido. | ❌ No con este número: consulta el documento autorizado | | `ALREADY_PROCESSED_NOT_CONFIRMED` | Solo producción, regla 90. La DIAN indicó que el documento fue procesado anteriormente, pero no confirmó la autorización de la clave. | Consulta su estado antes de reenviarlo | Los mensajes de `ATTACHED_DOCUMENT_NOT_GENERATED`, `PDF_NOT_GENERATED` y `EMAIL_NOT_SENT` terminan siempre en `No reenvíe el documento: ya es válido ante la DIAN.` Si el fallo se debe a una regla de negocio (por ejemplo, el límite de correos de tu plan), el mensaje incluye además `Motivo: ...`. :::tip Un warning nunca significa reenviar El documento de una respuesta con `warnings` ya tiene una respuesta de la DIAN. Reenviarlo no repara el paso que falló. Para obtener los archivos del documento usa los endpoints de [Consultas y Estados](https://docs.matias-api.com/docs/endpoints/queries-status.md). ::: **📋 Ejemplo — documento validado con correo no enviado** ```json { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "ad20e8c21f359c96389fb254104fe2282b0c9d0a1be8e651d09b73b927f267dfe0a07fc03b10b6f068e7d7ab1b15bcbc", "response": { "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura Electrónica LZT836, ha sido autorizada." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500000041.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000041.xml", "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4..." }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=ad20e8c2...", "url": "https://sandbox-api.matias-api.com/qr/1/QR-0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.png", "path": "1/QR-0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.png", "data": "iVBORw0KGgoAAAANSUhEUgAA..." }, "pdf": { "path": "1/fv09010914030002500000095.pdf", "url": "https://sandbox-api.matias-api.com/pdf/1/fv09010914030002500000095.pdf", "data": "JVBERi0xLjcKJeLjz9MK..." }, "warnings": [ { "code": "EMAIL_NOT_SENT", "message": "El documento fue validado por la DIAN, pero no se pudo enviar el correo. No reenvíe el documento: ya es válido ante la DIAN." } ], "success": true } ``` ### Regla 90 — "Documento procesado anteriormente" Solo en producción. Si la DIAN rechaza el envío con la regla 90, la API consulta en la DIAN el documento autorizado antes de responder: | Resultado de la consulta | Respuesta | |---|---| | La DIAN tiene autorizado ese documento | HTTP `200` con el **documento autorizado** (`IsValid: "true"`) y el warning `ALREADY_AUTHORIZED_RECOVERED`. El `message` es: `La DIAN indicó que el documento {número} ya había sido procesado. Se recuperó el documento que la DIAN autorizó (CUFE/CUDE {clave}); no se generó uno nuevo.` | | La DIAN autorizó ese número con otro contenido | Rechazo (HTTP `200`, `IsValid: "false"`) con el warning `ALREADY_AUTHORIZED_OTHER_CONTENT`; su texto también va en `message`. | | La DIAN no confirma la clave | Rechazo (HTTP `200`, `IsValid: "false"`) con el warning `ALREADY_PROCESSED_NOT_CONFIRMED`; su texto también va en `message`. | | La consulta falla (red, DIAN no disponible) | El rechazo llega tal como lo devolvió la DIAN, sin warning. | ### Reintento de un número ya transmitido Solo en producción. Cuando reenvías un número cuyo intento anterior se transmitió a la DIAN pero quedó sin validar (sin respuesta definitiva, en validación o interrumpido), la API **consulta primero la DIAN** en lugar de transmitir un XML nuevo con otro CUFE/CUDE: | Estado en la DIAN del intento anterior | Respuesta | |---|---| | Autorizado con el mismo contenido (número, emisor, tipo, total, adquiriente y, en notas, la factura referenciada) | HTTP `200` con ese documento y el warning `ALREADY_AUTHORIZED_RECOVERED`: `El documento {número} ya estaba autorizado por la DIAN (CUFE/CUDE {clave}). Se devuelve ese documento y no se transmitió de nuevo.` | | La DIAN aún lo está validando | HTTP `409` con `code: "DOCUMENT_IN_VALIDATION"`. No se transmite. | | No se pudo confirmar el estado | HTTP `503` con `code: "DIAN_STATUS_UNAVAILABLE"`. No se transmite. | | No autorizado, o autorizado con otro contenido | Se transmite normalmente. | La consulta no se hace si la DIAN ya había rechazado esa misma clave. En los casos `409` y `503` el número sigue reservado para ese documento: reintenta en unos minutos con el mismo número. ### 🆕 Documento recuperado: sin cobro de consumo Un documento que la DIAN ya tenía autorizado y que la API recupera (HTTP `200` con el warning `ALREADY_AUTHORIZED_RECOVERED`, por la [regla 90](https://docs.matias-api.com/docs/endpoints/invoices-pos#regla-90) o por la [consulta previa al reenvío](https://docs.matias-api.com/docs/endpoints/invoices-pos#reintento-transmitido)) queda válido **sin descontar documentos de tu plan**: ya se había emitido antes, por esta API o fuera de ella. Lo mismo ocurre cuando un documento queda válido más tarde, al consultar su estado en la DIAN. Por eso, el número de documentos válidos de un periodo puede ser mayor que el consumo cobrado a tu plan. ### Códigos de error | HTTP | `code` | Cuándo ocurre | Qué hacer | |---|---|---|---| | `400` | — | El documento con ese número ya está validado ante la DIAN (también si otro proceso lo validó mientras se transmitía este intento). `data` trae el registro del envío. | No lo reenvíes. Usa el documento existente. | | `400` | — | Se envió `send_to_queue: 1` (el envío a cola aún no está habilitado). | Omite el campo. Para procesamiento asíncrono usa [`POST /bulk/documents`](https://docs.matias-api.com/docs/endpoints/bulk-documents.md). | | `409` | `DOCUMENT_IN_TRANSMISSION` | Otra petición está transmitiendo a la DIAN el mismo documento (empresa, tipo y número). Si ese proceso se interrumpe, el número queda bloqueado como máximo 5 minutos. | Consulta el estado antes de enviarlo de nuevo. | | `409` | `DOCUMENT_IN_VALIDATION` | Solo producción. La DIAN aún valida un intento anterior de ese número. | Consulta el estado en unos minutos. | | `409` | — | El `uuid` que enviaste en el body ya pertenece a otro documento de la empresa. En `POST /auto-increment/...` un `uuid` ya registrado responde antes `400` (ver [UUID ya usado](https://docs.matias-api.com/docs/endpoints/autoincrement#uuid-ya-usado)). | Consulta ese documento o envía un `uuid` nuevo. | | `422` | — | Error de validación del JSON (formato estándar con `message` y `errors`). | Corrige el campo indicado. | | `503` | `DIAN_STATUS_UNAVAILABLE` | Solo producción. No se pudo confirmar en la DIAN el estado de un intento anterior de ese número. | Reintenta en unos minutos. | | `500` (normalmente) | `DOCUMENT_POSSIBLY_SENT` | Falló algo después de iniciar la transmisión (timeout, error al guardar): la DIAN pudo haber recibido el documento. El número se conserva. | Consulta el estado con el `uuid` del envío (`data.uuid`) antes de cualquier reintento. | Los errores `5xx` incluyen un campo `reference`: compártelo con soporte para ubicar el caso. **❌ 409 — `DOCUMENT_IN_TRANSMISSION`** ```json { "code": "DOCUMENT_IN_TRANSMISSION", "message": "El documento LZT836 ya se está transmitiendo a la DIAN en otra solicitud; consulte su estado antes de enviarlo de nuevo.", "data": { "document_number": "LZT836" }, "success": false } ``` **❌ 409 — `DOCUMENT_IN_VALIDATION`** ```json { "code": "DOCUMENT_IN_VALIDATION", "message": "La DIAN aún está validando el documento LZT836 (CUFE/CUDE ad20e8c21f35...). No se transmitió de nuevo: consulte su estado en unos minutos antes de reenviarlo.", "data": { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "document_number": "LZT836", "document_key": "ad20e8c21f35..." }, "success": false } ``` **❌ 503 — `DIAN_STATUS_UNAVAILABLE`** ```json { "success": false, "code": "DIAN_STATUS_UNAVAILABLE", "message": "No se pudo confirmar en la DIAN el estado del envío anterior del documento LZT836 (CUFE/CUDE ad20e8c21f35...). No se transmitió de nuevo para no duplicarlo: intente en unos minutos.", "data": { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "document_number": "LZT836", "document_key": "ad20e8c21f35..." } } ``` **❌ 500 — `DOCUMENT_POSSIBLY_SENT`** ```json { "success": false, "message": "No se obtuvo una respuesta definitiva para el documento LZT836 y es posible que la DIAN lo haya recibido. No lo emita de nuevo: consulte su estado con el uuid del envío antes de cualquier reintento. Detalle: Error interno del servidor.", "reference": "3f2b6c1e-9a4d-4e7b-8c21-5d0f7a9e1b42", "code": "DOCUMENT_POSSIBLY_SENT", "data": { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "document_number": "LZT836" } } ``` **❌ 409 — `uuid` ya usado en otro documento** ```json { "success": false, "message": "El uuid proporcionado ya se usó en otro documento de la empresa. No se emitió de nuevo: consulte ese documento o envíe un uuid nuevo." } ``` **❌ 400 — Envío a cola no habilitado** ```json { "success": false, "message": "El envío a cola de procesamiento aún no está habilitado." } ``` --- # 📦 Documento Soporte :::warning Autenticación Requerida Incluir en todos los endpoints: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Todos los endpoints aceptan el parámetro opcional `?client_uuid={{client_uuid}}`. Permite emitir documentos en nombre de empresas cliente cuando operas como **Casa de Software**. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: Los documentos soporte certifican adquisiciones efectuadas a sujetos **no obligados a expedir factura de venta** o documento equivalente. :::tip Orden de configuración recomendado Antes de emitir documentos soporte, asegúrate de tener configurados: **Resolución DIAN** → **Software DIAN** → **Certificado Digital**. ::: ## 📋 Diferencias clave: Documento Soporte vs Factura | Característica | Factura Electrónica | Documento Soporte | |---|---|---| | Endpoint | `POST /invoice` | `POST /ds/document` | | `type_document_id` | `1` (FEV) | No aplica (definido en payload) | | Proveedor | Obligado a facturar | **No obligado** a facturar | | Nota de ajuste | Nota Crédito/Débito | `POST /ds/adjustment-note` | | Referenciado en | `billing_reference` | `billing_reference` | 📦 Ver ejemplos en [/docs/jsons-support-document](https://docs.matias-api.com/docs/jsons-support-document/support-document.md) --- ## 1. Emisión **POST /ds/document — Enviar Documento Soporte** ```http POST {{url}}/ds/document?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Variantes disponibles:** - Documento Soporte **Residente**. - Documento Soporte **No Residente**. - Casos especiales: IVA + RTE IVA, con decimales. **cURL** ```bash curl -X POST "{{url}}/ds/document" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/ds/document`, payload, { headers: { Authorization: `Bearer ${token}` } }); const cude = response.data.XmlDocumentKey; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/ds/document", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => $payload, ]); ``` **✅ Respuesta Exitosa — Producción (HTTP 200)** ```json { "uuid": "7e3a91c4-7dd2-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "9fa2bd902a5b498c79a844ba5e70f3c67009fc5a0fea1cb0cd4aeac38a10a7425f0e08ad71b83c821372adb1771e07dd", "response": { "ErrorMessage": {}, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "El Documento Soporte DSE12, ha sido autorizado." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500000012.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000012.xml", "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4..." }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=9fa2bd90...", "url": "https://sandbox-api.matias-api.com/qr/1/QR-7e3a91c4-7dd2-11f0-ba9f-f02f74cac485.png", "path": "1/QR-7e3a91c4-7dd2-11f0-ba9f-f02f74cac485.png", "data": "iVBORw0KGgoAAAANSUhEUgAA..." }, "pdf": null, "warnings": [], "success": true } ``` `uuid` identifica el envío en la plataforma y `warnings` informa los pasos posteriores a la aceptación que hayan fallado (un warning nunca significa reenviar). Ver [Advertencias](https://docs.matias-api.com/docs/endpoints/invoices-pos#warnings-emision). **⚠️ Rechazo DIAN — Producción (HTTP 200, `IsValid: "false"`)** Un rechazo de la DIAN **no** es un error HTTP: llega con HTTP `200` y `success: true`, y el resultado fiscal está en `response.IsValid`, `response.StatusCode` y `response.ErrorMessage`. El HTTP `422` se reserva para errores de validación del JSON. ```json { "uuid": "7e3a91c4-7dd2-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "9fa2bd902a5b498c79a844ba5e70f3c67009fc5a0fea1cb0cd4aeac38a10a7425f0e08ad71b83c821372adb1771e07dd", "response": { "IsValid": "false", "StatusCode": "99", "ErrorMessage": { "string": ["Error en campos obligatorios del documento soporte."] } }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": null, "qr": null, "pdf": null, "warnings": [], "success": true } ``` :::info Mismas reglas que la factura El documento soporte usa el mismo flujo de emisión que `POST /invoice`: la respuesta de habilitación, los warnings de la regla 90 (`ALREADY_AUTHORIZED_*`), el `409` por `uuid` ya usado y los errores `409 DOCUMENT_IN_TRANSMISSION`, `409 DOCUMENT_IN_VALIDATION`, `503 DIAN_STATUS_UNAVAILABLE` y `DOCUMENT_POSSIBLY_SENT` aplican igual. Ver [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). ::: --- ## 2. Nota de Ajuste **POST /ds/adjustment-note — Enviar Nota de Ajuste** ```http POST {{url}}/ds/adjustment-note?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Uso:** Para ajustar valores de un documento soporte previamente emitido. **Variantes disponibles:** - Nota de ajuste a Documento Soporte **Residente** y **No Residente**. - Nota de ajuste con IVA + RTE IVA. **✅ Respuesta Exitosa — DIAN 200 OK** ```json { "uuid": "a41d07b8-7dd2-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "4c7e19d02b8a5f63e1d9a0b7c4f2e8a1d6b3c9f0e7a2d5b8c1f4e9a6d3b0c7e2f5a8d1b4c7e0f3a6d9b2c5e8f1a4d7b0", "response": { "ErrorMessage": {}, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Nota de Ajuste al Documento Soporte NAS3, ha sido autorizada." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500000013.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000013.xml", "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4..." }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=4c7e19d0...", "url": "https://sandbox-api.matias-api.com/qr/1/QR-a41d07b8-7dd2-11f0-ba9f-f02f74cac485.png", "path": "1/QR-a41d07b8-7dd2-11f0-ba9f-f02f74cac485.png", "data": "iVBORw0KGgoAAAANSUhEUgAA..." }, "pdf": null, "warnings": [], "success": true } ``` La nota de ajuste sigue las mismas reglas de respuesta, rechazo DIAN (HTTP `200` con `IsValid: "false"`) y errores que el documento soporte. Ver [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). --- # 💼 Nómina Electrónica :::warning Autenticación Requerida Incluir en todos los endpoints: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Todos los endpoints aceptan `?client_uuid={{client_uuid}}` para operar en nombre de empresas cliente. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: El Documento Soporte de Pago de Nómina Electrónica es el soporte de los costos y deducciones en el impuesto sobre la renta derivado de los pagos relacionados con la nómina. ## 📋 Tipos de Documentos de Nómina | Tipo | Endpoint | Uso | |------|----------|-----| | Nómina individual | `POST /ep/payroll` | Transmisión del período de pago | | Nota de reemplazo | `POST /ep/payroll/replace` | Corrección de una nómina enviada | | Nota de eliminación | `POST /ep/payroll/delete` | Eliminación legal de nómina errónea | 📦 Ver campos completos en [Campos de Nómina](https://docs.matias-api.com/docs/payroll/payroll-fields.md) --- ## 1. Emisión **POST /ep/payroll — Enviar Nómina** ```http POST {{url}}/ep/payroll?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Transmite el documento soporte de pago de nómina electrónica a la DIAN. **cURL** ```bash curl -X POST "{{url}}/ep/payroll" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/ep/payroll`, payload, { headers: { Authorization: `Bearer ${token}` } }); const cuneKey = response.data.XmlDocumentKey; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/ep/payroll", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => $payload, ]); ``` **✅ Respuesta Exitosa — DIAN 200 OK** ```json { "uuid": "c83f5e10-7dd3-11f0-ba9f-f02f74cac485", "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "860ff75995712a3bf5e9d14233e70bf2aca76b8ab5afdf41040e40331fe597c1bbb659a3af87e295a767a39921202e2e", "response": { "ErrorMessage": {}, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "El Documento Soporte de Pago de Nómina Electrónica NIE10, ha sido autorizado." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": null, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=860ff759..." }, "pdf": null, "warnings": [], "success": true } ``` | Campo | Descripción | |---|---| | `uuid` | Identificador del envío en la plataforma. | | `response` | Respuesta de la DIAN. El documento es válido solo con `IsValid: "true"` y `StatusCode: "00"`; un rechazo de la DIAN también llega con HTTP `200`. | | `AttachedDocument`, `pdf` | Siempre `null` en nómina: no se genera AttachedDocument, PDF ni correo. | | `warnings` | Normalmente `[]`. Si la DIAN validó el documento pero falla un paso posterior, trae `ATTACHED_DOCUMENT_NOT_GENERATED` y la respuesta **sigue siendo exitosa**: no reenvíes el documento, ya es válido ante la DIAN. | :::info Habilitación y errores - **Habilitación:** mientras el software de nómina está en pruebas, la respuesta tiene otra forma (incluye `XmlDocumentKey`, `ZipKey` y la respuesta del set de pruebas) y no trae `uuid` ni `warnings`. - **Documento ya validado:** si el número ya está validado ante la DIAN se responde HTTP `400` con el mensaje `El documento (...) con numero ... ya se encuentra validado.` y el registro del envío en `data`. - **Errores `5xx`:** incluyen un campo `reference` para que soporte ubique el caso. Estas reglas aplican también a `/ep/payroll/replace` y `/ep/payroll/delete`. ::: --- ## 2. Nota de Reemplazo **POST /ep/payroll/replace — Nota de Ajuste — Reemplazo** ```http POST {{url}}/ep/payroll/replace?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Uso:** Corregir un documento de nómina previamente enviado. La DIAN asume este nuevo documento como el reemplazo legal (corrección de valores). **✅ Respuesta Exitosa — DIAN 200 OK** ```json { "uuid": "d1a4b7e2-7dd3-11f0-ba9f-f02f74cac485", "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "...", "response": { "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Nota de Nómina Electrónica ha sido autorizada." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": null, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=..." }, "pdf": null, "warnings": [], "success": true } ``` --- ## 3. Nota de Eliminación **POST /ep/payroll/delete — Nota de Ajuste — Eliminación** ```http POST {{url}}/ep/payroll/delete?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Uso:** Eliminar un documento de nómina de forma legal ante la DIAN si fue generado por error y no se reemplazará por otro en el período actual. :::warning Consecuencias legales La nota de eliminación es una acción irreversible ante la DIAN. Úsala únicamente cuando el documento fue emitido por error y **no** va a ser reemplazado por una nueva nómina en el mismo período. ::: **✅ Respuesta Exitosa — DIAN 200 OK** ```json { "uuid": "e6b2c9f4-7dd3-11f0-ba9f-f02f74cac485", "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "...", "response": { "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Nota de Eliminación de Nómina ha sido autorizada." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": null, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=..." }, "pdf": null, "warnings": [], "success": true } ``` --- # 🔍 Búsqueda y Estados de Documentos :::info Autenticación Requerida Incluir en todos: `Authorization: Bearer {token}` ::: :::info ¿Dónde obtener el `client_uuid`? — Multi-Tenant para Casas de Software Si operas como **Casa de Software** o **Cuenta Principal**, puedes consultar documentos, estados, XMLs, PDFs y rangos de numeración de tus empresas cliente agregando el parámetro `client_uuid` en la URL: - **URL con Query Param:** `?client_uuid={{client_uuid}}` - **Header:** `Authorization: Bearer {token_cuenta_principal}` - **Comportamiento:** La consulta o descarga se ejecutará en el contexto de la empresa cliente especificada por su UUID. **¿Dónde encontrar el `client_uuid` de tus clientes?** ```http GET {{url}}/company/customers Authorization: Bearer {token} Content-Type: application/json ``` ::: :::tip GET para búsquedas · POST para descargas y acciones - 🟢 **GET** → Búsquedas, listados, consultas de datos - 🟠 **POST** → Descarga de archivos, envío de correos, actualización de estados ::: --- ## 📋 Búsqueda y Listado **GET /documents — Buscar Documentos** ```http GET {{url}}/documents?order_number=251956&query=&limit=1&resolution=&number=&prefix=&client_uuid={{client_uuid}} Authorization: Bearer {token} ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `order_number` | query | No | Número de orden interno. | | `number` | query | No | Número consecutivo del documento. | | `prefix` | query | No | Prefijo de la resolución. | | `resolution` | query | No | Número de resolución DIAN. | | `query` | query | No | Búsqueda por texto libre. | | `limit` | query | No | Registros por página (por defecto `20`, máximo `50`). | | `start_date` | query | No | Fecha inicial de emisión (`YYYY-MM-DD`). | | `end_date` | query | No | Fecha final de emisión (`YYYY-MM-DD`). | | `document_key` | query | No | CUFE o CUDE del documento. | | `document_type` | query | No | Tipo de documento (`type_document_id`, p. ej. `7` = Factura electrónica). | | `document_status` | query | No | Estado: `-1` = todos (por defecto), `0` = no validados por la DIAN (`validation_state` `NOT_VALIDATED`, `PENDING_REVALIDATION`, `NOT_FOUND_OR_REJECTED`, `CERTIFICATE_EXPIRED` o `PENDING`), `1` = solo validados. Los documentos en `PENDING_RESEND` (reenvío pendiente) no aparecen con `0`, solo con `-1`. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **cURL** ```bash curl -X GET "{{url}}/documents?limit=20&start_date=2026-08-01&end_date=2026-08-31" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/documents`, { params: { limit: 20, start_date: '2026-08-01', end_date: '2026-08-31' }, headers: { Authorization: `Bearer ${token}` } }); const docs = response.data.dataRecords.data; ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/documents", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'query' => ['limit' => 20, 'start_date' => '2026-08-01', 'end_date' => '2026-08-31'], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 1024, "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "XmlDocumentKey": "cf9864294501e8a9578235dd2ab3c4fd...", "is_valid": 1, "invoice_date": "01-08-2026" } ], "total": 142 }, "success": true } ``` **GET /documents/last — Último Documento Generado** ```http GET {{url}}/documents/last?resolution=18764074347312&prefix=LZT&client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Trae el último documento válido emitido para una resolución específica. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `resolution` | query | ✅ Sí | Número de resolución DIAN. | | `prefix` | query | No | Prefijo de la resolución. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "is_valid": true, "invoice_date": "2026-08-20T05:00:00.000000Z" } }, "success": true } ``` **GET /documents/consume — Consumo de Documentos** ```http GET {{url}}/documents/consume?p_year=2026&p_type=1&client_uuid={{client_uuid}} GET {{url}}/documents/consume?p_year=2026&p_type=6&_dni=900123456&client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve el reporte de documentos del año, agrupados según `p_type`. :::note Documentos válidos, no consumo cobrado En las agrupaciones `1` a `4`, `total` cuenta solo los documentos validados por la DIAN. Estas cifras no son el consumo cobrado de la membresía y pueden superar el `consumed` de [`GET /memberships/summary`](https://docs.matias-api.com/docs/endpoints/memberships-health#que-consume-del-plan): un documento que la API recupera porque la DIAN ya lo tenía autorizado, o que queda validado más tarde al consultar su estado, cuenta aquí pero no se cobra. Ver [Documento recuperado: sin cobro de consumo](https://docs.matias-api.com/docs/endpoints/invoices-pos#recuperado-sin-consumo). ::: **Parámetros:** Los parámetros se validan antes de consultar. | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `p_type` | query | No | 🆕 Agrupación (entero, por defecto `1`). Solo se aceptan estos valores: `1` = por mes y tipo de documento (la empresa y sus clientes); `2` = por año (la empresa y sus clientes); `3` = por cliente y mes (solo clientes); `4` = por cliente y año (solo clientes); `6` = un cliente (`_dni`) por mes y tipo de documento. El valor `5` ya no se acepta. | | `p_year` | query | No | 🆕 Año del reporte (entero entre `2000` y el año siguiente al actual). Por defecto, el año actual. | | `_dni` | query | Solo con `p_type=6` | 🆕 NIT del cliente sin dígito de verificación: solo dígitos, máximo 15 caracteres. También se acepta como `p_dni`. Las agrupaciones `1` a `4` no lo usan, pero si se envía debe tener un formato válido. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **cURL** ```bash curl -X GET "{{url}}/documents/consume?p_year=2026&p_type=6&_dni=900123456" \ -H "Authorization: Bearer {token}" \ -H "Accept: application/json" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/documents/consume`, { params: { p_year: 2026, p_type: 6, _dni: '900123456' }, headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); const rows = response.data.dataRecords.data; ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/documents/consume", [ 'headers' => ['Authorization' => "Bearer {$token}", 'Accept' => 'application/json'], 'query' => ['p_year' => 2026, 'p_type' => 6, '_dni' => '900123456'], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** Ejemplo con `p_type=6`: ```json { "dataRecords": { "data": [ { "nyear": 2026, "nmonth": "08", "total": 42, "payment": "12500000.00", "voucher_name": "Factura electrónica de venta", "company_name": "CLIENTE EJEMPLO S.A.S", "company_dni": "900123456", "parent_company": "CASA DE SOFTWARE EJEMPLO S.A.S", "parent_dni": "900654321" } ] }, "success": true } ``` Las columnas de cada fila dependen de `p_type`: | `p_type` | Columnas | Filas | |---|---|---| | `1` | `nyear`, `nmonth` (texto de 2 dígitos), `total`, `payment`, `parent_dni`, `parent_company`, `document_code`, `voucher_name` | Las de los clientes y las propias de la empresa, por separado, ordenadas por año, mes y código de documento. | | `2` | `nyear`, `total`, `payment`, `parent_dni`, `parent_company` | Una fila para los clientes y otra para la empresa. | | `3` | `nyear`, `nmonth` (texto de 2 dígitos), `total`, `payment`, `company_name`, `company_dni`, `parent_dni`, `parent_company` | Solo clientes, por cliente y mes. | | `4` | `nyear`, `total`, `payment`, `company_name`, `company_dni`, `parent_dni`, `parent_company` | Solo clientes, por cliente y año. | | `6` | `nyear`, `nmonth` (texto de 2 dígitos), `total`, `payment`, `voucher_name`, `company_name`, `company_dni`, `parent_company`, `parent_dni` | El cliente `_dni` de la empresa autenticada, por mes y tipo de documento. | **❌ Parámetros inválidos (HTTP 422)** Un `p_type` que no sea `1`, `2`, `3`, `4` o `6` (incluido el `5`), un `p_year` fuera de rango, un `_dni` no numérico o de más de 15 caracteres, o `p_type=6` sin `_dni`, responden **HTTP 422**. La clave del error es `_dni` aunque se haya enviado `p_dni`. ```json { "success": false, "message": "Error de validación de datos.", "errors": { "_dni": ["El campo dni es obligatorio cuando el campo p type es 6."] } } ``` Antes, un `p_type` fuera de rango respondía HTTP 500. **GET /documents/{uuid} — Consultar Documento por UUID 🆕** ```http GET {{url}}/documents/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve un documento de la empresa a partir de un UUID. Acepta cualquiera de estos dos identificadores: - El **uuid del envío**: el `uuid` que devuelve la respuesta de emisión (o el `document_uuid` de un item de envío masivo). - El **uuid anti-duplicados** que tú enviaste en el campo `uuid` del documento (o en `documents[].uuid` del envío masivo). :::tip Endpoint recomendado tras un `DOCUMENT_POSSIBLY_SENT` Si una emisión respondió con el código `DOCUMENT_POSSIBLY_SENT` (o un item masivo quedó en `POSSIBLY_SENT`), consulta este endpoint con `data.uuid` antes de cualquier reintento: así sabes si el documento quedó registrado y validado sin transmitirlo de nuevo. ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `uuid` | path | ✅ Sí | UUID del envío o UUID anti-duplicados enviado por el integrador. Debe tener formato UUID. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **cURL** ```bash curl -X GET "{{url}}/documents/dde72910-eb42-11ef-9b27-f02f74cac485" \ -H "Authorization: Bearer {token}" \ -H "Accept: application/json" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/documents/${uuid}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); const record = response.data.dataRecord; // null si no existe ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/documents/{$uuid}", [ 'headers' => ['Authorization' => "Bearer {$token}", 'Accept' => 'application/json'], ]); ``` **✅ Respuesta Exitosa con el uuid del envío (HTTP 200)** Se muestran los campos principales del envío. El `id` es el identificador numérico que usa `DELETE /documents/{id}`. ```json { "dataRecord": { "id": 1024, "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "type_document_id": 7, "XmlDocumentKey": "cf9864294501e8a9578235dd2ab3c4fd...", "XmlDocumentName": "fv09010914030002500000095", "order_number": "251956", "is_valid": 1, "invoice_date": "14-02-2025", "jsonData": {}, "createdAt": "14-02-2025 10:15:00 am", "updatedAt": "14-02-2025 10:15:04 am" }, "success": true } ``` **✅ Respuesta Exitosa con el uuid anti-duplicados (HTTP 200)** Con el uuid que envió el integrador, `dataRecord` es el vínculo entre ese uuid y el envío; el envío viene en `shipping_history`. Se muestran los campos principales. ```json { "dataRecord": { "shipping_history_uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "shipping_history": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "is_valid": 1 } }, "success": true } ``` **ℹ️ Documento inexistente (HTTP 200)** Si ningún documento de la empresa tiene ese uuid, la respuesta es **HTTP 200** con `dataRecord: null` (no 404): ```json { "dataRecord": null, "success": true } ``` **❌ UUID con formato inválido (HTTP 400)** ```json { "success": false, "message": "El UUID proporcionado no es válido." } ``` **DELETE /documents/{id} — Eliminar Documento No Validado 🆕** ```http DELETE {{url}}/documents/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Elimina el registro de un documento de la empresa **solo si la DIAN no lo tiene**. El `id` es el identificador numérico del envío (campo `id` de `GET /documents` o de `GET /documents/{uuid}`). **Se puede eliminar** cuando su `validation_state` (ver `GET /status`) es `NOT_VALIDATED` o `NOT_FOUND_OR_REJECTED`, y además: - nunca se transmitió (no tiene CUFE/CUDE), o - la DIAN devolvió un rechazo definitivo para ese mismo CUFE/CUDE. **No se puede eliminar** un documento validado, uno en cualquier otro estado intermedio (`PENDING_REVALIDATION` tras un `StatusCode 98`, `CERTIFICATE_EXPIRED`, `PENDING_RESEND`, `PENDING`), uno rechazado por la regla 90 ("documento procesado anteriormente") ni uno cuyo envío no obtuvo una respuesta definitiva de la DIAN. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | Id numérico del envío. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **cURL** ```bash curl -X DELETE "{{url}}/documents/1024" \ -H "Authorization: Bearer {token}" \ -H "Accept: application/json" ``` **JavaScript (Axios)** ```js const response = await axios.delete(`${url}/documents/${id}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } }); ``` **PHP (Guzzle)** ```php $response = $client->delete("{$url}/documents/{$id}", [ 'headers' => ['Authorization' => "Bearer {$token}", 'Accept' => 'application/json'], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Documento eliminado correctamente.", "success": true } ``` **❌ Respuestas de Error** | HTTP | `code` | `message` | |---|---|---| | 404 | — | `No se encontró el documento.` | | 400 | — | `No se puede eliminar un documento validado por la DIAN.` | | 400 | — | `No se puede eliminar un documento que la DIAN podría tener o cuya validación está pendiente.` | | 409 | `DOCUMENT_IN_TRANSMISSION` | El número se está transmitiendo a la DIAN en otra solicitud. | | 409 | — | `El documento cambió mientras se eliminaba; consulte su estado.` | ```json { "code": "DOCUMENT_IN_TRANSMISSION", "message": "El documento LZT836 ya se está transmitiendo a la DIAN en otra solicitud; consulte su estado antes de enviarlo de nuevo.", "success": false } ``` --- ## 📥 Descarga de Archivos Para descargar los adjuntos y representaciones gráficas del documento usando el **CUFE/CUDE** (`trackId`). **POST /documents/pdf/{trackId} — Descargar PDF** ```http POST {{url}}/documents/pdf/{trackId}?regenerate=0&client_uuid={{client_uuid}} Authorization: Bearer {token} ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | ✅ Sí | CUFE/CUDE del documento. | | `regenerate` | query | No | `1` fuerza regenerar el PDF, `0` descarga el existente. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | :::tip QR del PDF regenerado El QR de un PDF generado corresponde siempre al CUFE/CUDE consultado (`trackId`). Si un documento se transmitió más de una vez y su PDF quedó con el QR de otro intento, `regenerate=1` genera el PDF de nuevo con el QR correcto. Con `regenerate=0` se entrega el PDF existente tal cual. ::: **cURL** ```bash curl -X POST "{{url}}/documents/pdf/{{trackId}}?regenerate=0" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.post( `${url}/documents/pdf/${trackId}?regenerate=0`, {}, { headers: { Authorization: `Bearer ${token}` } } ); const pdfUrl = response.data.pdf.url; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/documents/pdf/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'query' => ['regenerate' => 0], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "PDF generado correctamente.", "pdf": { "path": "1/b46e15b0-69db-11f1-984a-345a60fd780b.pdf", "url": "https://api.ejemplo.com/pdf/1/b46e15b0-69db-11f1-984a-345a60fd780b.pdf", "data": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago..." } } ``` **❌ trackId no encontrado (HTTP 404)** ```json { "success": false, "message": "No se puede generar el PDF. No se encontró un documento asociado al trackId: cf9864294501e8a9578235dd2ab3c4fd..." } ``` **GET /documents/xml/{trackId} — Descargar XML** ```http GET {{url}}/documents/xml/{trackId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` También acepta `POST` en la misma ruta. :::note Solo planes de pago La descarga del XML no está disponible en el plan FREE: responde **HTTP 403** con `Esta funcionalidad no está disponible en el plan FREE. Actualice su plan para acceder.` ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | ✅ Sí | CUFE/CUDE del documento. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | El XML entregado corresponde siempre al CUFE/CUDE solicitado: si el documento no está validado y el XML guardado pertenece a otro intento del mismo número (otra clave), se obtiene de la DIAN el XML de la clave pedida. **❌ trackId no encontrado (HTTP 404)** Un `trackId` que no pertenece a la empresa autenticada responde: ```json { "success": false, "message": "No se encontró un documento asociado al trackId: cf9864294501e8a9578235dd2ab3c4fd..." } ``` **POST /documents/attached/{trackId} — Descargar Adjunto (Attached Document)** ```http POST {{url}}/documents/attached/{trackId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Descarga el `.zip` con el ApplicationResponse completo. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | ✅ Sí | CUFE/CUDE del documento. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **GET /documents/{uuid}/files — Obtener Archivos del Documento** ```http GET {{url}}/documents/{uuid}/files?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Retorna los archivos asociados a un documento (PDF, XML, ApplicationResponse) identificado por su UUID interno. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `uuid` | path | ✅ Sí | UUID interno del documento. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "document_uuid": "1868f3c9-98fb-11f1-b521-345a60fd780b", "uuid": "102035600026107941", "file_path": "xml/1/fv/fv636afa85-c756-463e-b28c-0c0fa8adf3c3.xml", "file_name": "fv636afa85-c756-463e-b28c-0c0fa8adf3c3.xml", "type": "xml" } ], "total": 3 } } ``` --- ## 📊 Consulta de Estados **POST /status/document/{trackId} — Estado en Producción** ```http POST {{url}}/status/document/{trackId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Verifica el estado del documento directamente con la DIAN en producción. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | ✅ Sí | CUFE/CUDE del documento emitido. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **cURL** ```bash curl -X POST "{{url}}/status/document/{{trackId}}" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.post( `${url}/status/document/${trackId}`, {}, { headers: { Authorization: `Bearer ${token}` } } ); const isValid = response.data.is_valid === 1; const dian = response.data.response; // IsValid, StatusCode, StatusDescription... ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/status/document/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** La respuesta de la DIAN llega en `response`, ya extraída del sobre SOAP (sin `Envelope` ni `Body`). ```json { "message": "Consulta generada con éxito", "response": { "ErrorMessage": { "string": [] }, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura electrónica LZT836, ha sido autorizada.", "XmlDocumentKey": "cf9864294501e8a9578235dd2ab3c4fd...", "XmlFileName": "fv09010914030002500000095" }, "is_valid": 1, "success": true } ``` **📌 Reglas de `is_valid`** - `is_valid` es `1` solo si la DIAN responde `IsValid: "true"` con `StatusCode: "00"`, el CUFE/CUDE de la respuesta coincide con el `trackId` consultado (o la DIAN no lo devuelve) y el documento conserva ese CUFE/CUDE. - Si la DIAN responde la regla 90 ("documento procesado anteriormente"), `is_valid` es `0`. - Un documento ya validado **nunca se degrada**: si la DIAN no lo confirma en esta consulta, la respuesta trae `is_valid: 0` pero el documento sigue registrado como validado. **❌ Respuestas de Error** | HTTP | `code` | `message` | |---|---|---| | 404 | — | `No se encontró el documento con el trackId: {trackId}` (el `trackId` no pertenece a la empresa). | | 409 | `DOCUMENT_IN_TRANSMISSION` | Ese número se está transmitiendo a la DIAN en otra solicitud; consulte de nuevo en unos instantes. | ```json { "message": "No se encontró el documento con el trackId: cf9864294501e8a9578235dd2ab3c4fd...", "success": false } ``` **POST /status/zip/{trackId} — Estado en Pruebas (ZIP)** ```http POST {{url}}/status/zip/{trackId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | ✅ Sí | Track ID del envío en pruebas. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | :::note A diferencia de `POST /status/document/{trackId}`, este endpoint y `POST /status/document/test/{trackId}` conservan la clave `ResponseDian` en la respuesta. ::: **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Consulta generada con éxito", "ResponseDian": { "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "Documento verificado y validado por la DIAN." }, "success": true } ``` **POST /status/document/test/{trackId} — Estado en Pruebas (Test)** ```http POST {{url}}/status/document/test/{trackId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Verifica el estado de un documento en el ambiente de **pruebas** (habilitación) directamente con la DIAN. A diferencia del ZIP, usa el trackId del documento de prueba. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | ✅ Sí | CUFE/CUDE del documento de prueba. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Consulta generada con éxito", "ResponseDian": { "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "Documento verificado y validado por la DIAN." }, "success": true } ``` **GET /status — Estado Interno de la API** ```http GET {{url}}/status?order_number=251956&resolution=&number=LZT836&prefix=&client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Obtiene información del documento registrado en el API, su estado de validación y los datos del Código QR. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `order_number` | query | No | Número de orden interno. | | `number` | query | No | Número de documento. | | `prefix` | query | No | Prefijo de la resolución. | | `resolution` | query | No | Número de resolución. | | `type_document_id` | query | No | 🆕 Tipo de documento (entero mayor que 0). También se acepta como `document_type`. Un valor no numérico se ignora. Útil cuando dos tipos de documento comparten numeración. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **¿Qué documento se devuelve?** Si la búsqueda incluye `number` y hay varios registros que coinciden, se prefiere el validado por la DIAN y, después, el más reciente. Sin `number`, se devuelve el más reciente. **✅ Respuesta Exitosa (HTTP 200)** ```json { "document": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "order_number": "251956", "document_key": "cf9864294501e8a9578235dd2ab3c4fd1d9085fe5d3b345d191fbb8c9afa6ff8acec7a97a177393b2d32735d225a9f1d", "document_name": "fv09010914030002500000095", "is_valid": true, "validation_state": "VALID", "type_document_id": 7, "invoice_date": "2025-02-14T05:00:00.000000Z", "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=cf9864...", "data": "TnVtRmFjOiBMWlQ4MzYK...", "path": null, "url": null } }, "status": "Validado por la DIAN", "message": "Consulta exitosa", "success": true } ``` - `is_valid` es `true` **solo** si la DIAN validó el documento. Los estados intermedios (pendiente de consulta, reenvío pendiente, etc.) responden `false`; consulta `validation_state` para saber en qué punto está. - `qr.qrDian` y `qr.data` corresponden a `document_key`, el CUFE/CUDE vigente del documento. `qr.path` y `qr.url` llegan en `null`. - `qr.qrDian` es la URL de consulta de la DIAN con `document_key`. Conserva el ambiente (producción o habilitación) con el que se emitió el documento; si no hay una URL guardada, se arma con el ambiente del software configurado para ese tipo de documento. Llega en `null` si no hay una URL guardada y el documento no tiene CUFE/CUDE o no hay software configurado. - `qr.data` es el texto del QR original en base64 (NumFac, FecFac, ..., CUFE) **solo** si contiene `document_key`. Si no lo contiene (por ejemplo, en un documento recuperado con `ALREADY_AUTHORIZED_RECOVERED` o con un QR guardado de otro intento), es la URL de `qr.qrDian` codificada en base64. Si no hay un QR original válido y `qr.qrDian` es `null`, también llega en `null`. **📌 Valores de `validation_state` y `status`** | `validation_state` | `status` | Significado | |---|---|---| | `VALID` | `Validado por la DIAN` | La DIAN validó el documento. | | `NOT_VALIDATED` | `Sin validación por la DIAN` | Sin validación (rechazo o sin respuesta válida). | | `PENDING_REVALIDATION` | `Sin validación por la DIAN: pendiente de consultar su estado` | Pendiente de consultar su estado en la DIAN (por ejemplo, tras un `StatusCode 98`). | | `NOT_FOUND_OR_REJECTED` | `Sin validación por la DIAN: la DIAN no tiene el documento o lo rechazó` | La consulta a la DIAN no lo encontró o lo rechazó. | | `CERTIFICATE_EXPIRED` | `Sin validación por la DIAN: certificado vencido` | No se pudo consultar su estado porque el certificado está vencido. | | `PENDING_RESEND` | `Sin validación por la DIAN: reenvío pendiente` | Marcado para reenvío a la DIAN. | | `PENDING` | `Sin validación por la DIAN: pendiente de envío` | Pendiente de envío a la DIAN. | | `UNKNOWN` | `Sin validación por la DIAN` | Estado no reconocido. | **❌ Documento no encontrado (HTTP 404)** ```json { "success": false, "message": "No se encontró el documento." } ``` --- ## 📧 Correos y Adquirentes **POST /documents/sendmail/to · /documents/sendmail/{trackId} — Envío y Reenvío de Correos** ```http POST {{url}}/documents/sendmail/to?client_uuid={{client_uuid}} POST {{url}}/documents/sendmail/{trackId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Permite enviar un correo libre con archivos en base64 (`/sendmail/to`) o reenviar por CUFE/CUDE un documento ya validado (`/sendmail/{trackId}`) al adquirente u otros destinatarios. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `trackId` | path | Solo en `/sendmail/{trackId}` | CUFE/CUDE (`XmlDocumentKey`) de un documento de la empresa. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **Body:** | Campo | Endpoint | Requerido | Descripción | |---|---|---|---| | `email_to` | Ambos | ✅ Sí | Una o varias direcciones separadas por `,` o `;` (más de 10 caracteres en total). En `/sendmail/{trackId}` no puede contener espacios; en `/sendmail/to` los espacios se eliminan. | | `subject` | `/sendmail/to` | No | Asunto del correo. | | `title` | `/sendmail/to` | No | Título del cuerpo del correo. | | `message` | `/sendmail/to` | No | Texto del cuerpo del correo. | | `documents` | `/sendmail/to` | No | Archivos a adjuntar: arreglo de objetos con `name`, `content` (base64) y `mime`. | | `attachments` | `/sendmail/to` | No | Adjuntos adicionales del usuario, con la misma estructura del campo [`attachments`](https://docs.matias-api.com/docs/billing-fields#attachments-new) de la emisión. | **cURL** ```bash curl -X POST "{{url}}/documents/sendmail/{{trackId}}" \ -H "Authorization: Bearer {token}" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"email_to": "compras@cliente-ejemplo.com;contabilidad@cliente-ejemplo.com"}' ``` **JavaScript (Axios)** ```js const response = await axios.post( `${url}/documents/sendmail/to`, { email_to: 'compras@cliente-ejemplo.com', subject: 'Factura LZT836', title: 'Factura electrónica', message: 'Adjuntamos su factura.', documents: [{ name: 'factura.pdf', content: pdfBase64, mime: 'application/pdf' }] }, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' } } ); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/documents/sendmail/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}", 'Accept' => 'application/json'], 'json' => ['email_to' => 'compras@cliente-ejemplo.com'], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** El envío es asíncrono: la respuesta confirma que el proceso inició y no devuelve la lista de destinatarios. ```json { "message": "Se ha iniciado el proceso de envío del correo electrónico.", "success": true } ``` En `/sendmail/{trackId}` el correo de una factura adjunta el ZIP (PDF + AttachedDocument) más los adjuntos que haya cargado el usuario; el PDF no se adjunta suelto. **🛡️ Validación de destinatarios (HTTP 400)** Antes de encolar el correo, en los dos endpoints se valida **cada** destinatario: el formato de la dirección y que su dominio pueda recibir correo. Si **una sola** dirección no puede recibir correo, se bloquea el envío completo: ```json { "success": false, "message": "El envío fue bloqueado automáticamente porque la(s) siguiente(s) dirección(es) no puede(n) recibir correo. Verifique que estén bien escritas: compras@dominio-inexistente.com (el dominio dominio-inexistente.com no existe o no recibe correo), ventas@ (formato inválido)" } ``` Si algún destinatario figura en la lista de supresión (por rebotes permanentes o quejas de spam anteriores), también se bloquea con HTTP 400: `El envío fue bloqueado automáticamente porque la(s) siguiente(s) dirección(es) de destinatario(s) se encuentra(n) en nuestra lista de supresión (...)`. Esta validación ocurre antes de buscar el documento: un correo inválido responde 400 aunque el `trackId` no exista. **❌ Otros errores de `/sendmail/{trackId}`** | HTTP | `message` | |---|---| | 400 | `Debe indicar, al menos un correo, a donde desea enviar el documento.` | | 404 | `El trackId del documento no existe en la base de datos.` | | 409 | `El documento no está validado por la DIAN y no puede enviarse por correo.` | | 404 | `No se encontró el documento para el envío de correo.` | | 400 | `No se puede enviar un correo electrónico a consumidor final.` | | 400 | `El documento nómina electrónica no está soportado para el envío de email.` | | 409 | `El documento {id} ya tiene N trabajo(s) de envío en proceso. Intente más tarde.` | | 400 | `Límite de destinatarios excedido. Máximo permitido: X, solicitados: Y` | | 400 | `No se pudo enviar el email a ningún destinatario. Errores: ...` | | 500 | `Error interno del servidor. Hay espacios en blanco en la lista de correos electrónicos.` (incluye `reference`) | En `/sendmail/to` aplica el mismo error 400 cuando falta `email_to` o es demasiado corto. **GET /acquirer — Consulta de Adquirente** ```http GET {{url}}/acquirer?identificationType=13&identificationNumber=1063279303&client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Retorna información asociada (como correos) de un Adquirente registrado previamente en las facturas. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `identificationType` | query | ✅ Sí | Tipo de identificación (`13`=CC, `31`=NIT, etc.). | | `identificationNumber` | query | ✅ Sí | Número de documento del adquirente. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Consulta generada con éxito", "content": { "ReceiverEmail": "correo@cliente.com", "ReceiverName": "CLIENTE EJEMPLO S.A.S", "StatusCode": "200" }, "customer": { "id": 748, "country_id": 45, "city_id": 149, "identity_document_id": 3, "company_name": "CLIENTE EJEMPLO S.A.S", "dni": "901091403", "email": "correo@cliente.com" } } ``` **GET /exchange-emails — Intercambio de Correos** ```http GET {{url}}/exchange-emails?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Obtiene la lista de correos registrados para recepción en la plataforma. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | --- ## 🔢 Numeración **GET /numbering-range — Rango de Numeración** ```http GET {{url}}/numbering-range?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Obtiene las resoluciones y rangos de numeración activos de facturación. :::tip Sincronización con la DIAN Agrega `?sync=1` para forzar la sincronización del rango de numeración con los servicios SOAP de la DIAN (`GetNumberingRange`). ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `type_id` | query | No | Tipo de documento: `1`=Factura, `3`=Doc Soporte, `4`=POS. | | `sync` | query | No | `1` fuerza sincronización con la DIAN. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "id": 1, "resolution_number": "18764074347312", "prefix": "LZT", "from_number": 1, "to_number": 5000000, "current_number": 836, "date_from": "2024-01-01", "date_to": "2027-12-31", "is_active": true } ] }, "success": true } ``` --- # 🔄 API de Eventos RADIAN :::info Base URL, Autenticación & Multi-Tenant (`client_uuid`) - **Base URL:** `/api/ubl2.1/events` - **Autenticación:** Requiere header `Authorization: Bearer {token}` en todos los endpoints. - **Plan requerido:** Todo el módulo de recepción (eventos, importación, buzón y personas de recepción) exige un **plan de pago vigente**. Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan). - **Casas de Software (Multi-Tenant):** Puedes gestionar, consultar y transmitir eventos RADIAN en nombre de tus empresas cliente agregando el parámetro `?client_uuid={{client_uuid}}` a cualquiera de las URLs de esta sección. **¿Dónde encontrar el `client_uuid` de tus clientes?** Puedes consultar el listado completo de tus empresas cliente y sus respectivos `client_uuid` mediante el endpoint: ```http GET {{url}}/company/customers Authorization: Bearer {token} Content-Type: application/json ``` ::: **RADIAN** es la plataforma de la DIAN que permite la circulación electrónica de la Factura Electrónica de Venta (FEV) como título valor negociable. A través de esta API puedes registrar y transmitir los eventos del ciclo de vida de una factura desde la perspectiva del adquiriente, recibir facturas de tus proveedores en un buzón de correo y administrar las personas que firman los eventos. ### Requisito de plan Todos los endpoints de `/api/ubl2.1/events/*` (recepciones, estado, archivos, importación, envío individual y en bloque, reenvío de correo, cancelación, eliminación, buzón y personas de recepción) exigen que la empresa tenga un **plan de pago vigente**: suscripción activa, no vencida y de un plan distinto de FREE. * Un plan **FREE**, un plan de pago **vencido** o una empresa **sin suscripción** reciben `403` con `error_code: RECEPTION_NOT_IN_PLAN`. Esta respuesta se emite **antes** de validar el body, así que tiene prioridad sobre cualquier `422`. * Quedarse **sin saldo** de documentos **no** quita el módulo: el plan ilimitado y los contratos mensuales o pospago tienen acceso mientras estén vigentes. * Para una **sub-cuenta**, cuenta el plan de su Casa de Software. * La empresa se determina con `client_uuid` (query o body) o, sin él, con la empresa del token. Si el `client_uuid` no es tuyo, la API responde el `403` habitual de acceso, que **no** trae `error_code`: así puedes distinguir un problema de plan de uno de permisos. * Un cambio de plan puede tardar hasta **60 segundos** en reflejarse en la API. * En el **Sandbox** el módulo siempre está incluido: este `403` no se emite. ```json { "success": false, "message": "El plan FREE no incluye la recepción de facturas ni los eventos RADIAN. Actualice su plan para usarlos.", "error_code": "RECEPTION_NOT_IN_PLAN" } ``` El mensaje es el mismo para plan FREE, plan vencido o empresa sin suscripción. Usa `error_code` para mostrar un aviso de actualización de plan sin cerrar la sesión del usuario. :::caution 🆕 Migración: `MAILBOX_NOT_IN_PLAN` ya no se emite El buzón (`GET /mailbox`, `GET /mailbox/{id}`, `GET /mailbox/address` y el resto de sus endpoints) es parte del módulo de recepción y responde este mismo `403` con `error_code` `RECEPTION_NOT_IN_PLAN`. Ya no devuelve `error_code` `MAILBOX_NOT_IN_PLAN` ni el mensaje «El plan FREE no incluye el buzón de recepción…». Si tu integración comparaba `error_code === "MAILBOX_NOT_IN_PLAN"`, compárala con `RECEPTION_NOT_IN_PLAN`. ::: ### Ciclo de vida de una factura en RADIAN El flujo estándar de eventos es secuencial. **Importar el documento es opcional:** si envías un evento con `POST /send/{trackId}` sobre un CUFE/CUDE que la empresa aún no tiene registrado, la API descarga el documento de la DIAN y lo registra en ese momento. ``` [Importar CUFE (opcional)] → [030 Acuse de Recibo] → [032 Recibo del Bien] → [033 Aceptación Expresa] ↘ [031 Reclamo] ``` :::caution Solo facturas de venta a crédito La DIAN solo admite eventos RADIAN sobre **facturas electrónicas de venta a crédito**. Las facturas de **contado**, las facturas **sin medio de pago** (se asumen de contado) y las **notas crédito/débito** no generan ni admiten eventos: quedan registradas y visibles, con el motivo en el campo `status_reason`. ::: ### Eventos automáticos vs. manuales El **modo de eventos** es una configuración de la **empresa** (`send_events`), que se cambia con [`PUT /reception-people/mode`](https://docs.matias-api.com/docs/endpoints/events-radian#modo-eventos): | Modo | `send_events` | Qué ocurre al registrar una factura a crédito (buzón o importación por Excel/CUFE) | |------|:---:|------| | **Automático** | `true` | Se crean los eventos `030`, `032` y `033` y se envían a la DIAN sin intervención del usuario. El `033` automático quita la posibilidad de reclamar con el `031`. El `031` nunca es automático. | | **Manual** | `false` | No se crea **ningún** evento, **tampoco el `030`**. El usuario los envía con `POST /send/{trackId}` o `POST /send-bulk`. | Reglas: * **Una empresa nueva arranca en modo manual.** * Si la empresa pasa a manual, los eventos que quedaron pendientes de envío automático **no** salen a la DIAN. * Lo que pide el usuario (`POST /send/{trackId}` y `POST /send-bulk`) **siempre se envía**, también en modo manual. * Sin persona de recepción, el buzón se trata como manual y la importación por Excel/CUFE responde `400` `No tiene una persona de recepción asignada` (salvo que envíes `reception_person` en la misma solicitud). * El envío automático solo toma eventos pendientes creados en los **últimos 3 días** y de empresas con plan de pago vigente. Si la empresa pierde el plan, sus pendientes no se envían; al volver a un plan vigente, salen los de los últimos 3 días. * Nunca se duplica un evento del mismo tipo para el mismo documento, aunque la empresa cambie de numeración. * Un evento **aceptado** por la DIAN consume un documento del plan **una sola vez**. ### Tipos de Evento Soportados | Código | Nombre Oficial DIAN | Requiere `claim_code` | |--------|---------------------|-----------------------| | `030` | Acuse de Recibo de Factura Electrónica | No | | `031` | Reclamo de la Factura Electrónica de Venta | **Sí** | | `032` | Recibo del Bien y/o Prestación del Servicio | No | | `033` | Aceptación Expresa | No | --- ## 📥 1. Importación y Encolamiento :::info Prerrequisitos de la importación Antes de importar, la empresa necesita, en este orden: 1. Una **numeración activa para eventos de recepción**. Sin ella: `400` `No tiene una numeración activa para los eventos de recepción`. 2. Una **persona de recepción** (ver [Personas de recepción](https://docs.matias-api.com/docs/endpoints/events-radian#personas-recepcion)), salvo que envíes sus datos en `reception_person`. Sin ella: `400` `No tiene una persona de recepción asignada`. Si envías un `reception_person_uuid` que no es de la empresa: `404`. El procesamiento es **asíncrono**: el resultado de cada documento se consulta en [`GET /document-receptions`](https://docs.matias-api.com/docs/endpoints/events-radian#consultas) (campos `import_status` y `status_reason`). Los eventos automáticos solo se crean si la empresa está en [modo automático](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos). ::: :::note Persona elegida al importar La persona que indicas al importar (`reception_person_uuid` o `reception_person`) se guarda con la recepción y **solo firma los eventos automáticos** (`030`, `032` y `033` en modo automático) que transmite el envío programado. * En **modo manual** la importación no crea eventos, así que esa persona no firma nada. * Los envíos que pides tú ([`POST /send/{trackId}`](https://docs.matias-api.com/docs/endpoints/events-radian#envio-eventos) y [`POST /send-bulk`](https://docs.matias-api.com/docs/endpoints/events-radian#envio-en-bloque)) firman con el `reception_person_uuid` de **esa** solicitud o, sin él, con la persona por defecto de la empresa. No usan la persona elegida al importar. Si trabajas en modo manual y quieres que firme una persona distinta de la por defecto, envía `reception_person_uuid` en cada envío. ::: **POST /import-excel — Importar desde Excel** Permite importar múltiples documentos a RADIAN desde el archivo Excel `.xlsx`/`.xls` que exporta la DIAN. El archivo debe enviarse codificado en **base64**. Máximo **500 filas** por archivo. **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `document_base64` | `string` | **Sí** | Archivo Excel (`.xlsx`/`.xls`) codificado en base64. Máximo **5 MB** decodificado. | | `reception_person_uuid` | `string` (uuid) | No | Persona de recepción que firma los eventos **automáticos** de todo el lote. Sin él, se usa la persona por defecto. Ver [Persona elegida al importar](https://docs.matias-api.com/docs/endpoints/events-radian#persona-importacion). | | `reception_person` | `object` | No | Datos de quien firma, enviados en la misma solicitud (ver tabla abajo). Si se envía, se ignora `reception_person_uuid`. Se aplica a todo el lote. | **Objeto `reception_person` (opcional):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `identity_document_id` | `integer` | **Sí** | Tipo de documento de identidad (catálogo `GET {{url}}/identity-documents`) | | `dni` | `string` (máx. 20) | **Sí** | Número de identificación | | `dv` | `string` (máx. 2) | No | Dígito de verificación | | `first_name` | `string` (máx. 60) | **Sí** | Nombres | | `last_name` | `string` (máx. 60) | **Sí** | Apellidos | | `job_title` | `string` (máx. 100) | No | Cargo | | `department` | `string` (máx. 100) | No | Área o departamento | > [!WARNING] > El envío de archivos binarios (`multipart/form-data`) ya no es soportado: la API responde `422` con un mensaje que explica cómo enviar el archivo en base64 dentro de un JSON. **cURL** ```bash curl -X POST "{{url}}/events/import-excel" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "document_base64": "UEsDBBQABgAIAAAAIQBi7p..." }' ``` **JavaScript (Axios)** ```js const fs = require('fs'); const fileBase64 = fs.readFileSync('facturas.xlsx').toString('base64'); const response = await axios.post(`${url}/events/import-excel`, { document_base64: fileBase64 }, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }); ``` **PHP (Guzzle)** ```php $fileBase64 = base64_encode(file_get_contents('facturas.xlsx')); $response = $client->post("{$url}/events/import-excel", [ 'headers' => [ 'Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json', ], 'json' => [ 'document_base64' => $fileBase64, ], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Importación procesada.", "total_rows": 18, "queued": 6, "skipped": 10, "cash": 2, "skipped_details": [ { "row": 5, "cufe": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "reason_code": "DOCUMENT_TYPE_NOT_ALLOWED", "message": "Tipo de documento 'Nota de crédito electrónica' no admite eventos RADIAN: solo se procesan facturas electrónicas de venta." } ], "errors": [ "Fila 9: CUFE/CUDE vacío." ], "success": true } ``` **Explicación de la respuesta:** | Campo | Tipo | Descripción | |-------|------|-------------| | `total_rows` | `int` | Total de filas leídas del Excel | | `queued` | `int` | Documentos encolados para procesamiento (incluye los CUFE ya registrados que se reprocesan: los que fallaron antes o los que eliminó el sistema) | | `skipped` | `int` | Filas omitidas (las de `skipped_details` y las de `errors`) | | `cash` | `int` | Facturas de **contado** según la columna `Forma de Pago` = `1`: se registran completas y visibles sin consultar la DIAN y **sin eventos**. No cuentan en `queued` ni en `skipped`. | | `skipped_details` | `array` | Detalle de cada fila omitida: `row`, `cufe`, `reason_code` y `message` | | `errors` | `array` | Errores por fila, como texto (p. ej. `Fila N: NIT receptor (x) no coincide con la empresa (y).` o `Fila N: CUFE/CUDE vacío.`) | **Valores de `reason_code` en `skipped_details`:** | `reason_code` | Motivo | |---|---| | `GROUP_NOT_RECEIVED` | La fila no pertenece al grupo `Recibido` | | `DUPLICATED_IN_FILE` | El CUFE/CUDE está repetido en el archivo | | `DOCUMENT_TYPE_NOT_ALLOWED` | Notas y otros tipos: solo se procesan facturas electrónicas de venta | | `ALREADY_IMPORTED` | El CUFE/CUDE ya está registrado; `message` trae el motivo (ya importado, de contado, omitido o `El cufe/cude ya está en proceso de importación.`) | | `USER_DELETED` | El usuario eliminó esa recepción: `El documento fue eliminado de la recepción; para recuperarlo impórtelo de nuevo por su CUFE.` La importación por Excel **no** restaura lo que el usuario eliminó. | **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `400` | `El archivo excede el límite de 500 filas. Filas encontradas: N.` | Más de 500 filas en el archivo | | `400` | `No tiene una numeración activa para los eventos de recepción` | La empresa no tiene numeración activa de eventos | | `400` | `No tiene una persona de recepción asignada` | Sin persona de recepción y sin `reception_person` | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `404` | `La persona de recepción indicada (reception_person_uuid) no existe para esta empresa` | `reception_person_uuid` ajeno o eliminado | | `422` | `El archivo decodificado no puede superar los 5MB.` | Archivo de más de 5 MB | | `422` | `El archivo debe ser de tipo Excel (.xlsx o .xls).` | El contenido no es un Excel | | `422` | `El campo document base64 no contiene un string base64 válido.` | `document_base64` no es base64 válido | Los `422` de validación siguen el formato `{"message": "...", "errors": {"campo": ["..."]}}`. **POST /import-track-id — Importar por Track ID** Importa un documento individual a RADIAN usando su CUFE o CUDE. **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `trackId` | `string` | **Sí** | CUFE o CUDE del documento a importar (entre 10 y 255 caracteres) | | `reception_person_uuid` | `string` (uuid) | No | Persona de recepción que firma los eventos **automáticos** de este documento. Sin él, se usa la persona por defecto. Ver [Persona elegida al importar](https://docs.matias-api.com/docs/endpoints/events-radian#persona-importacion). | | `reception_person` | `object` | No | Datos de quien firma, con los mismos campos que en [`/import-excel`](https://docs.matias-api.com/docs/endpoints/events-radian#importacion). Si se envía, se ignora `reception_person_uuid`. | :::tip Ruta alternativa También existe `POST /{trackId}/import`, pero esa ruta **también exige** el campo `trackId` en el body (sin él responde `422` `No ha enviado un trackId.`); cuando el body lo trae, se usa el de la URL. Recomendamos usar `POST /import-track-id`. ::: **cURL** ```bash curl -X POST "{{url}}/events/import-track-id" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "trackId": "{{trackId}}" }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/import-track-id`, { trackId: trackId }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/import-track-id", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => ['trackId' => $trackId], ]); ``` **💻 Ver Respuesta Exitosa (200)** Documento nuevo: ```json { "message": "Documento encolado para procesamiento.", "id": 42, "success": true } ``` **Reimportación** de un CUFE ya registrado que fue eliminado (por el usuario o por el sistema) o cuya importación falló: se restaura y se vuelve a encolar. ```json { "message": "Documento encolado nuevamente para procesamiento.", "id": 42, "success": true } ``` **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `400` | `El cufe/cude ya ha sido importado.` (más el motivo, si lo hay; p. ej. contado) | El documento ya está registrado y completo | | `400` | `No tiene una numeración activa para los eventos de recepción` | La empresa no tiene numeración activa de eventos | | `400` | `No tiene una persona de recepción asignada` | Sin persona de recepción y sin `reception_person` | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `404` | `La persona de recepción indicada (reception_person_uuid) no existe para esta empresa` | `reception_person_uuid` ajeno o eliminado | | `409` | `El cufe/cude ya está en proceso de importación.` | El documento se está importando | | `409` | `El cufe/cude ya ha sido importado.` seguido del motivo | El documento fue omitido (p. ej. una nota crédito/débito) | | `422` | `No ha enviado un trackId.` / `El trackId debe tener al menos 10 caracteres.` / `El trackId no puede superar los 255 caracteres.` / `reception_person_uuid debe ser un UUID válido.` | Validación del body | Si al procesarlo el plan ya no incluye el módulo, el documento queda con `import_status` `FAILED` y el motivo del plan en `status_reason`. Una nota crédito/débito o un tipo no soportado queda `SKIPPED` con su motivo (no se borra). --- ## 🔍 2. Consultas y Detalles :::tip Flujo recomendado `import-track-id` → `document-receptions` (espera `import_status` = `COMPLETED` y `status_reason` = `null`) → `send/{trackId}` Un documento con `status_reason` distinto de `null` no admite eventos o aún se está importando. ::: **GET /document-receptions — Listar Recepciones** **Query Params:** | Parámetro | Tipo | Default | Descripción | |-------|------|---------|-------------| | `trackId` | `string` | — | Buscar por CUFE/CUDE exacto. Si se envía, se ignoran `query`, `startDate` y `endDate`. | | `query` | `string` | — | Buscar por folio, razón social o NIT del emisor | | `startDate` | `string` | — | Fecha inicio (`YYYY-MM-DD`) de **registro en la plataforma** (no de emisión) | | `endDate` | `string` | — | Fecha fin (`YYYY-MM-DD`) de registro. Se compara a medianoche: no incluye los registros de ese mismo día. | | `limit` | `int` | 20 | Registros por página (máx. 50) | | `client_uuid` | `string` | — | UUID del cliente (Multi-Tenant) | El resultado se ordena por fecha de emisión, de la más reciente a la más antigua. **cURL** ```bash curl -X GET "{{url}}/events/document-receptions?startDate=2026-08-01&endDate=2026-08-31&limit=20" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/document-receptions`, { params: { startDate: '2026-08-01', endDate: '2026-08-31', limit: 20 }, headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/document-receptions", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'query' => ['startDate' => '2026-08-01', 'endDate' => '2026-08-31', 'limit' => 20], ]); ``` **💻 Ver Respuesta Exitosa (200)** Se muestran los campos principales de cada recepción. ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 42, "cufe_cude": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "folio": "FE1001", "issue_date": "15-08-2026", "total": "1190000.00", "document_origin": "RECEIVED", "import_status": "COMPLETED", "status_reason": null, "people": { "id": 7, "dni": "900123456", "company_name": "PROVEEDOR DE EJEMPLO S.A.S.", "email": "facturacion@proveedor-ejemplo.com", "address": "Calle 1 # 2-3", "phone": "3000000000" } } ], "per_page": 20, "total": 18 }, "success": true } ``` **Campos de estado de cada recepción:** | Campo | Descripción | |-------|-------------| | `import_status` | `PENDING`, `PROCESSING`, `COMPLETED`, `SKIPPED` (omitido, p. ej. una nota) o `FAILED` | | `document_origin` | `IMPORTED` (Excel, CUFE o envío de evento) o `RECEIVED` (llegó por el buzón) | | `status_reason` | `null` si el documento admite eventos. Si no, el motivo legible (tabla abajo). | **Valores de `status_reason`:** | Situación | Texto | |---|---| | Factura de contado | `Factura de contado: la DIAN no admite eventos RADIAN (acuse, recibo, aceptación o reclamo) para facturas de contado.` | | Nota omitida (`SKIPPED`) | `Nota crédito/débito: la DIAN no admite eventos RADIAN sobre notas; solo se procesan facturas electrónicas de venta.` | | Tipo no soportado (`SKIPPED`) | `Tipo de documento 'X' no soportado para eventos RADIAN: solo se procesan facturas electrónicas de venta (01).` | | Importación fallida (`FAILED`) | `No fue posible obtener el documento desde la DIAN. Puede volver a importar el CUFE.` | | Documento ilegible (`FAILED`) | `El documento que devolvió la DIAN para este CUFE no es una factura electrónica legible. Verifique el CUFE o contacte a soporte.` | | Sin plan al procesar (`FAILED`) | `El plan FREE no incluye la recepción de facturas ni los eventos RADIAN. Actualice su plan para usarlos.` | | En proceso (`PENDING` / `PROCESSING`) | `El documento se está importando; el resultado aparecerá en el listado en unos segundos.` | **GET /document-receptions/{documentId} — Detalle de Recepción con Eventos** Devuelve la recepción con su arreglo `events`. Ahí está el **`id` numérico de cada evento**, que necesitas para [cancelarlo](https://docs.matias-api.com/docs/endpoints/events-radian#cancelar-evento) o [reenviar su correo](https://docs.matias-api.com/docs/endpoints/events-radian#reenvio-correo), y su `document_status` (`PENDING`, `PROCESSING`, `ACCEPTED`, `REJECTED`, `CANCELLED` o `UNKNOWN`), que muestra el resultado final de un [envío en bloque](https://docs.matias-api.com/docs/endpoints/events-radian#envio-en-bloque). **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `documentId` | `int` | ID de la recepción (campo `id` del listado) | Si el `documentId` no existe o es de otra empresa, la respuesta es `200` con `data` vacío (no `404`). **💻 Ver Respuesta Exitosa (200)** Se muestran los campos principales. ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 42, "cufe_cude": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "folio": "FE1001", "issue_date": "15-08-2026", "total": "1190000.00", "document_origin": "IMPORTED", "import_status": "COMPLETED", "status_reason": null, "people": { "id": 7, "dni": "900123456", "company_name": "PROVEEDOR DE EJEMPLO S.A.S.", "email": "facturacion@proveedor-ejemplo.com", "address": "Calle 1 # 2-3", "phone": "3000000000" }, "events": [ { "id": 128, "type_event_id": 1, "event_number": "EV1", "date_event": "16-08-2026 09:30:00 am", "description": "Acuso recibido de factura.", "document_status": "ACCEPTED", "send_mail": 1 } ] } ], "total": 1 }, "success": true } ``` **GET /document-receptions/{documentId}/files — Archivos de una Recepción** Lista los archivos custodiados de una recepción que llegó por el [buzón de recepción](https://docs.matias-api.com/docs/endpoints/events-radian#buzon-recepcion). Las recepciones importadas por Excel o CUFE no tienen archivos y devuelven una lista vacía. **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `documentId` | `int` | ID de la recepción | **cURL** ```bash curl -X GET "{{url}}/events/document-receptions/42/files" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/document-receptions/${documentId}/files`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/document-receptions/{$documentId}/files", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "dataRecords": { "data": [ { "id": 9001, "type_document": "XML", "file_name": "document.xml", "mime_type": "application/xml", "file_size": 18432, "created_at": "2026-08-15T14:20:00.000000Z" }, { "id": 9002, "type_document": "ZIP", "file_name": "fe1001.zip", "mime_type": "application/zip", "file_size": 9216, "created_at": "2026-08-15T14:20:00.000000Z" } ] }, "success": true } ``` | Campo | Descripción | |-------|-------------| | `id` | ID del archivo, para la descarga | | `type_document` | `INBOUND_EMAIL_RAW` (correo original, `original.eml`), `XML` (el documento electrónico, `document.xml`), `ZIP` (el adjunto, con el nombre que traía en el correo) o `ATTACHED_DOC` (cuando el documento llegó como AttachedDocument XML suelto en lugar de ZIP) | | `file_size` | Tamaño en bytes (puede ser `null`) | **❌ Recepción no encontrada (HTTP 404)** ```json { "success": false, "message": "Recepción de documento no encontrada." } ``` **GET /document-receptions/{documentId}/files/{fileManagerId}/download — Descargar Archivo** Devuelve el **binario** del archivo con su `Content-Type` y el encabezado `Content-Disposition: attachment; filename="..."`. **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `documentId` | `int` | ID de la recepción | | `fileManagerId` | `int` | `id` del archivo (de `/files` o de `files` en `GET /mailbox/{id}`) | **cURL** ```bash curl -X GET "{{url}}/events/document-receptions/42/files/9001/download" \ -H "Authorization: Bearer {token}" \ -o fe1001.xml ``` **JavaScript (Axios)** ```js const response = await axios.get( `${url}/events/document-receptions/${documentId}/files/${fileId}/download`, { headers: { Authorization: `Bearer ${token}` }, responseType: 'arraybuffer' } ); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/document-receptions/{$documentId}/files/{$fileId}/download", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'sink' => 'fe1001.xml', ]); ``` **❌ Ver Respuestas de Error (HTTP 404)** | Mensaje | Causa | |---|---| | `Recepción de documento no encontrada.` | La recepción no existe o es de otra empresa | | `Archivo no encontrado para esta recepción de documento.` | El archivo no pertenece a esa recepción | | `El archivo no está disponible en el almacenamiento.` | El archivo ya no está almacenado | :::caution 🆕 El 403 de plan llega en JSON Sin plan de pago vigente, la descarga responde `403` en **JSON**, aunque hayas pedido un binario ([Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan)): ```json { "success": false, "message": "El plan FREE no incluye la recepción de facturas ni los eventos RADIAN. Actualice su plan para usarlos.", "error_code": "RECEPTION_NOT_IN_PLAN" } ``` Si pides la descarga como `blob` o `arraybuffer`, lee el cuerpo del error como texto JSON para obtener el `error_code`. Por ejemplo, con Axios: `JSON.parse(new TextDecoder().decode(error.response.data))` con `arraybuffer`, o `JSON.parse(await error.response.data.text())` con `blob`. ::: **GET /status/{trackId} — Consultar Estado de Evento** **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `trackId` | `string` | CUFE o CUDE del documento | **cURL** ```bash curl -X GET "{{url}}/events/status/{{trackId}}" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/status/${trackId}`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/status/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** `ResponseDian` trae la respuesta de la consulta a la DIAN. ```json { "message": "Consulta generada con éxito", "ResponseDian": {}, "success": true } ``` --- ## 📤 3. Envío de Eventos a la DIAN Todos los eventos se envían al mismo endpoint. El tipo de evento se determina por el campo `code` en el body. **Endpoint:** `POST {{url}}/events/send/{{trackId}}` | Parámetro | Ubicación | Tipo | Requerido | Descripción | |-----------|-----------|------|:---:|-------------| | `trackId` | Path | `string` | **Sí** | CUFE o CUDE del documento recibido | | `code` | Body | `string` | **Sí** | Código del evento RADIAN (`030`–`033`) | | `notes` | Body | `string` | No | Descripción o nota del evento (máx. 200 caracteres) | | `claim_code` | Body | `string` | Solo `031` | ⚠️ **Solo evento `031`** — Código del motivo del reclamo | | `reception_person_uuid` | Body | `string` (uuid) | No | Persona de recepción que firma el evento. Sin él, firma la persona por defecto. | :::info Reglas del envío individual * **Síncrono:** la respuesta llega después de transmitir a la DIAN. Es `200` también cuando la DIAN **rechaza** el evento: queda en `REJECTED` y `data` trae la respuesta de la DIAN. Cuando la DIAN lo acepta, `data` es `{"IsValid": "true"}`. * **Sin importación previa:** si la empresa no tiene recepción para ese CUFE/CUDE, la API descarga el documento de la DIAN y lo registra (`document_origin` = `IMPORTED`). Solo admite facturas y notas (tipos `01`, `91`, `92`); luego aplica la regla de crédito: contado o nota responden `400` con el motivo. Sin medio de pago en el XML se asume contado. * **Sin duplicados:** si el documento ya tiene un evento de ese tipo (en cualquier numeración), se reutiliza. Se reenvía un evento `PENDING`, `REJECTED`, `CANCELLED` o `UNKNOWN`; nunca uno `ACCEPTED`. 🆕 Si el evento reutilizado se creó con una numeración distinta de la vigente, al reenviarlo (aquí o con [`/send-bulk`](https://docs.matias-api.com/docs/endpoints/events-radian#envio-en-bloque)) pasa a la numeración activa de eventos y toma su siguiente número, así que cambia el número del evento en el detalle de la recepción. Si ya está en la numeración vigente, conserva su número. * **Sale también en modo manual.** * Si otro proceso (envío automático, envío en bloque u otra petición) está transmitiendo el mismo evento, responde `409`: no se envía en paralelo ni se cobra dos veces. Si ese envío falla, puedes reintentar. ::: **❌ Respuestas de error comunes a los eventos 030, 031, 032 y 033** Cuerpo: `{"success": false, "message": "..."}`, salvo el `422` de validación (formato `{"message": "...", "errors": {...}}`) y el `403` de plan. | HTTP | Mensaje | Causa | |:---:|---|---| | `400` | `El tipo de evento no existe` | `code` inválido | | `400` | `El código de reclamación es obligatorio.` | Evento `031` sin `claim_code` | | `400` | `El código de reclamación no existe.` | `claim_code` inválido | | `400` | `No tiene una persona de recepción asignada` | La empresa no tiene persona de recepción | | `400` | `Factura de contado: la DIAN no admite eventos RADIAN (acuse, recibo, aceptación o reclamo) para facturas de contado.` | Documento de contado (o sin medio de pago) | | `400` | `Nota crédito/débito: la DIAN no admite eventos RADIAN sobre notas; solo se procesan facturas electrónicas de venta.` | El documento es una nota (o el `status_reason` de un documento omitido) | | `400` | `El tipo de documento no es permitido` | El CUFE/CUDE descargado de la DIAN no es factura ni nota | | `400` | `No tiene una numeración activa para los eventos de recepción` | La empresa no tiene numeración activa de eventos | | `400` | `El evento ya fue validado y aceptado.` | El evento ya está `ACCEPTED` | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `404` | `La persona de recepción indicada (reception_person_uuid) no existe para esta empresa` | `reception_person_uuid` ajeno o eliminado (sin efectos) | | `409` | `El evento ya se está enviando.` | Otro proceso lo está transmitiendo | | `422` | `reception_person_uuid debe ser un UUID válido.` | `reception_person_uuid` mal formado | --- ### Evento 030 — Acuse de Recibo **POST /send/{trackId} — `030` Acuse de Recibo de Factura Electrónica** El adquiriente confirma que recibió la factura electrónica. **Es el primer evento obligatorio** antes de poder enviar cualquier otro. **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `code` | `string` | **Sí** | Fijo: `"030"` | | `notes` | `string` | No | Nota o descripción del acuse | | `reception_person_uuid` | `string` (uuid) | No | Persona que firma; sin él, la persona por defecto | **cURL** ```bash curl -X POST "{{url}}/events/send/{{trackId}}" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "code": "030", "notes": "Acuso recibido de factura." }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/send/${trackId}`, { code: '030', notes: 'Acuso recibido de factura.' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/send/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'code' => '030', 'notes' => 'Acuso recibido de factura.', ], ]); ``` **💻 Ver Respuesta Exitosa (200)** `message` es `Evento creado con éxito` cuando el evento es nuevo y `Evento enviado con éxito` cuando se reenvía uno existente. ```json { "message": "Evento creado con éxito", "data": { "IsValid": "true" }, "success": true } ``` --- ### Evento 031 — Reclamo de la FEV **POST /send/{trackId} — `031` Reclamo de la Factura Electrónica de Venta** El adquiriente registra un reclamo sobre la factura. Requiere el campo adicional `claim_code` con el motivo del reclamo. :::caution Prerrequisito Solo puede enviarse después de haber enviado el evento **030** (Acuse de Recibo). Si la empresa está en [modo automático](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos), el `033` automático quita la posibilidad de reclamar. ::: **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `code` | `string` | **Sí** | Fijo: `"031"` | | `notes` | `string` | No | Descripción del reclamo | | `claim_code` | `string` | **Sí** | Motivo del reclamo (ver tabla abajo) | | `reception_person_uuid` | `string` (uuid) | No | Persona que firma; sin él, la persona por defecto | **Valores válidos para `claim_code`:** | `claim_code` | Descripción | |---|---| | `01` | Documento con inconsistencias | | `02` | Mercancía no entregada | | `03` | Mercancía entregada parcialmente | | `04` | Servicio no prestado | :::tip Endpoint de consulta Puedes obtener siempre la lista actualizada de conceptos de reclamo desde: ```http GET {{url}}/claim-concepts ``` ::: **cURL** ```bash curl -X POST "{{url}}/events/send/{{trackId}}" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "code": "031", "notes": "Reclamo de la Factura Electrónica de Venta.", "claim_code": "01" }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/send/${trackId}`, { code: '031', notes: 'Reclamo de la Factura Electrónica de Venta.', claim_code: '01' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/send/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'code' => '031', 'notes' => 'Reclamo de la Factura Electrónica de Venta.', 'claim_code' => '01', ], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Evento creado con éxito", "data": { "IsValid": "true" }, "success": true } ``` --- ### Evento 032 — Recibo del Bien y/o Servicio **POST /send/{trackId} — `032` Recibo del Bien y/o Prestación del Servicio** El adquiriente confirma que recibió el bien o que el servicio fue prestado conforme a lo acordado. :::caution Prerrequisito Solo puede enviarse después de haber enviado el evento **030** (Acuse de Recibo). ::: **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `code` | `string` | **Sí** | Fijo: `"032"` | | `notes` | `string` | No | Nota del recibo del bien/servicio | | `reception_person_uuid` | `string` (uuid) | No | Persona que firma; sin él, la persona por defecto | **cURL** ```bash curl -X POST "{{url}}/events/send/{{trackId}}" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "code": "032", "notes": "Recibo del bien y/o prestación del servicio." }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/send/${trackId}`, { code: '032', notes: 'Recibo del bien y/o prestación del servicio.' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/send/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'code' => '032', 'notes' => 'Recibo del bien y/o prestación del servicio.', ], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Evento creado con éxito", "data": { "IsValid": "true" }, "success": true } ``` --- ### Evento 033 — Aceptación Expresa **POST /send/{trackId} — `033` Aceptación Expresa** El adquiriente acepta expresamente la factura como título valor, habilitándola para su circulación y negociación en el mercado de valores. :::caution Prerrequisito Solo puede enviarse después de haber enviado los eventos **030** y **032** en ese orden. ::: **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `code` | `string` | **Sí** | Fijo: `"033"` | | `notes` | `string` | No | Nota de la aceptación expresa | | `reception_person_uuid` | `string` (uuid) | No | Persona que firma; sin él, la persona por defecto | **cURL** ```bash curl -X POST "{{url}}/events/send/{{trackId}}" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "code": "033", "notes": "Aceptación expresa." }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/send/${trackId}`, { code: '033', notes: 'Aceptación expresa.' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/send/{$trackId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'code' => '033', 'notes' => 'Aceptación expresa.', ], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Evento creado con éxito", "data": { "IsValid": "true" }, "success": true } ``` --- ### Envío en bloque del acuse 030 **POST /send-bulk — Enviar el acuse `030` a varias recepciones 🆕** Envía el acuse de recibo (`030`) a varias recepciones de la empresa en una sola solicitud. Es la forma práctica de acusar en [modo manual](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos). **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `document_ids` | `array` de `int` | **Sí** | IDs de las recepciones (campo `id` de `GET /document-receptions`). Mínimo 1, máximo **50** por solicitud. Los repetidos se envían una sola vez. | | `code` | `string` | **Sí** | Solo se admite `"030"` | | `reception_person_uuid` | `string` (uuid) | No | Persona que firma; sin él, la persona por defecto | | `client_uuid` | `string` | No | UUID de la empresa cliente (Casa de Software). También puede ir en el query. | :::info Reglas del envío en bloque * **Asíncrono:** `queued` significa **encolado**, no aceptado. El resultado final de cada documento se consulta en [`GET /document-receptions/{documentId}`](https://docs.matias-api.com/docs/endpoints/events-radian#consultas) (`document_status` del evento). * **Siempre se envía**, también si la empresa está en modo manual. * **Idempotente:** nunca crea un segundo `030` para el mismo documento. Un `030` `ACCEPTED` o en envío no se reenvía; uno `PENDING`, `REJECTED`, `CANCELLED` o `UNKNOWN` sí se envía. * Un evento encolado por esta vía pasa de inmediato a envío (`PROCESSING`) y **no se puede cancelar** con `/{id}/cancel`. * 🆕 **Plan perdido con el envío en cola:** si al momento de transmitir el `030` la empresa ya no tiene plan de pago vigente (bajó a FREE o venció), no se envía ni se cobra. El evento queda `REJECTED` y se notifica con el motivo del plan. Con un plan de pago vigente puedes volver a enviarlo (`/send/{trackId}` o `/send-bulk`). En cambio, un evento **automático** que ya estaba en cola vuelve a `PENDING` y sale solo si la empresa recupera el plan dentro de la ventana de 3 días. ::: **cURL** ```bash curl -X POST "{{url}}/events/send-bulk" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "document_ids": [42, 43, 44], "code": "030" }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/send-bulk`, { document_ids: [42, 43, 44], code: '030' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/send-bulk", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'document_ids' => [42, 43, 44], 'code' => '030', ], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Envío en bloque procesado.", "summary": { "requested": 3, "queued": 1, "already_exists": 1, "not_applicable": 1, "not_found": 0, "error": 0 }, "results": [ { "document_id": 42, "folio": "FE1001", "status": "queued", "message": "Acuse encolado para envío a la DIAN.", "event_id": 128, "document_status": "PROCESSING" }, { "document_id": 43, "folio": "FE1002", "status": "already_exists", "message": "El acuse ya fue aceptado por la DIAN.", "event_id": 129, "document_status": "ACCEPTED" }, { "document_id": 44, "folio": "FE1003", "status": "not_applicable", "message": "Factura de contado: la DIAN no admite eventos RADIAN (acuse, recibo, aceptación o reclamo) para facturas de contado.", "event_id": null, "document_status": null } ], "success": true } ``` **Valores de `status` en `results`:** | `status` | `message` | |---|---| | `queued` | `Acuse encolado para envío a la DIAN.` | | `already_exists` | `El acuse ya fue aceptado por la DIAN.` o `El acuse ya se está enviando a la DIAN.` | | `not_applicable` | Motivo legible: contado, nota crédito/débito, documento omitido, importación en proceso o fallida, o sin plan | | `not_found` | `La recepción no existe para esta empresa.` | | `error` | `No fue posible encolar el acuse. Intente de nuevo.` | `event_id` es el `id` numérico del evento (sirve para `/send/mail/{id}`); `folio`, `event_id` y `document_status` pueden ser `null`. **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `400` | `No tiene una persona de recepción asignada` | Sin `reception_person_uuid` y sin persona de recepción | | `400` | `No tiene una numeración activa para los eventos de recepción` | Solo si hay algo que enviar; no se crea nada | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `404` | `La persona de recepción indicada (reception_person_uuid) no existe para esta empresa` | `reception_person_uuid` ajeno o eliminado (sin efectos) | | `404` | `Ninguna de las recepciones indicadas existe para esta empresa.` | Ningún `document_id` es de la empresa | | `422` | `Debe indicar las recepciones (document_ids).` | Falta `document_ids` | | `422` | `Puede enviar como máximo 50 recepciones por solicitud.` | Demasiados elementos | | `422` | `Cada document_id debe ser un número entero.` | Elemento no entero | | `422` | `En bloque solo se admite el acuse de recibo (030).` | `code` distinto de `030` | | `422` | `reception_person_uuid debe ser un UUID válido.` | UUID mal formado | Los `422` siguen el formato `{"message": "...", "errors": {"campo": ["..."]}}`; los demás, `{"success": false, "message": "..."}`. --- ## 📧 4. Reenvío de Correo **POST /send/mail/{trackId} — Reenviar Correo de Evento** Reenvía el correo de notificación asociado a un evento ya procesado. El envío del correo se encola. Máximo **4 correos por evento**. **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `trackId` | `int` | **ID numérico del evento** (el `id` de `events` en `GET /document-receptions/{documentId}` o el `event_id` de `send-bulk`). **No** es el CUFE/CUDE. | **cURL** ```bash curl -X POST "{{url}}/events/send/mail/128" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const eventId = 128; const response = await axios.post(`${url}/events/send/mail/${eventId}`, {}, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $eventId = 128; $response = $client->post("{$url}/events/send/mail/{$eventId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Correo enviado correctamente", "success": true } ``` **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `400` | `Se ha alcanzado el límite de envío de correos. No puede enviar más de 4 correos por evento.` | Ya se enviaron 4 correos de ese evento | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `404` | `El evento no existe.` | El evento no existe o es de otra empresa (misma respuesta) | --- ## 🚫 5. Cancelación de Eventos Encolados **POST /{id}/cancel — Cancelar Evento en Estado PENDING** ```http POST {{url}}/events/{id}/cancel?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Permite cancelar un evento RADIAN mientras se encuentre en estado `PENDING`, antes de que el proceso automático de envío lo transmita a la DIAN. :::info Reglas de Transición * **Transición Exclusiva:** Únicamente permite transicionar de `PENDING` → `CANCELLED`. * **Sin conflictos con el envío automático:** la cancelación y el envío automático nunca actúan a la vez sobre el mismo evento. * **Estados no Cancelables:** Cualquier estado distinto de `PENDING` (`PROCESSING`, `ACCEPTED`, `REJECTED`, `CANCELLED`, `UNKNOWN`) se rechaza con HTTP 400. * Los eventos encolados por [`/send-bulk`](https://docs.matias-api.com/docs/endpoints/events-radian#envio-en-bloque) pasan de inmediato a envío (`PROCESSING`) y no son cancelables. * Un evento cancelado puede volver a enviarse con `POST /send/{trackId}` o `POST /send-bulk`. * En [modo manual](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos) los eventos `PENDING` no se envían automáticamente. ::: **Parámetros:** | Parámetro | Ubicación | Tipo | Requerido | Descripción | |-----------|-----------|------|:---------:|-------------| | `id` | Path | `integer` | ✅ Sí | ID numérico del evento (el `id` de `events` en `GET /document-receptions/{documentId}`; **no** el CUFE/trackId). | | `client_uuid` | Query | `string` | No | UUID de la empresa cliente para operaciones multi-tenant (Casa de Software). | **cURL** ```bash curl -X POST "{{url}}/events/128/cancel" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" ``` **JavaScript (Axios)** ```js const eventId = 128; const response = await axios.post(`${url}/events/${eventId}/cancel`, {}, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }); ``` **PHP (Guzzle)** ```php $eventId = 128; $response = $client->post("{$url}/events/{$eventId}/cancel", [ 'headers' => [ 'Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json', ], ]); ``` **✅ Respuesta Exitosa (HTTP 200 OK)** `data` trae el evento con su nuevo estado (se muestran los campos principales). ```json { "message": "Evento cancelado con éxito", "data": { "id": 128, "document_reception_id": 42, "type_event_id": 1, "document_status": "CANCELLED" }, "success": true } ``` **❌ Evento en un estado distinto de PENDING (HTTP 400 Bad Request)** ```json { "success": false, "message": "Solo se pueden cancelar eventos en estado PENDING." } ``` **❌ Evento no encontrado o no pertenece a la empresa (HTTP 404 Not Found)** ```json { "success": false, "message": "El evento no existe." } ``` **❌ Plan sin módulo de recepción (HTTP 403 Forbidden)** ```json { "success": false, "message": "El plan FREE no incluye la recepción de facturas ni los eventos RADIAN. Actualice su plan para usarlos.", "error_code": "RECEPTION_NOT_IN_PLAN" } ``` --- ## 🗑️ 6. Eliminación de Recepción **DELETE /document-receptions/{id} — Eliminar Recepción** **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `id` | `int` | ID de la recepción | :::caution Restricción de Eliminación No se puede eliminar una recepción si ya tiene eventos con estado `ACCEPTED` o `PROCESSING`. Al eliminarla se borran también sus demás eventos (pendientes, rechazados o cancelados). ::: :::tip Recuperar una recepción eliminada Si vuelves a importar ese CUFE con `POST /import-track-id`, la recepción se restaura y se reprocesa (`Documento encolado nuevamente para procesamiento.`). La importación por Excel **no** la restaura: la reporta en `skipped_details` con `reason_code` `USER_DELETED`. ::: **cURL** ```bash curl -X DELETE "{{url}}/events/document-receptions/42" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.delete(`${url}/events/document-receptions/${documentId}`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->delete("{$url}/events/document-receptions/{$documentId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "message": "Recepción de documento eliminada exitosamente.", "success": true } ``` **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `400` | `No se puede eliminar una recepción con eventos válidos o en procesamiento.` | Tiene eventos `ACCEPTED` o `PROCESSING` | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `404` | `Recepción de documento no encontrada.` | No existe o es de otra empresa | --- ## 📬 7. Buzón de Recepción :::info 🆕 Nuevo · Buzón de correo para recibir facturas Cada empresa tiene **una** dirección de correo, con la forma `{identificador}@{dominio}`, a la que sus proveedores envían las facturas electrónicas. La API lee los adjuntos de cada correo, registra los documentos como recepciones (`document_origin` = `RECEIVED`) y, según el [modo de eventos](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos), crea o no sus eventos. * La dirección se asigna al registrar la **primera persona de recepción** ([`POST /reception-people`](https://docs.matias-api.com/docs/endpoints/events-radian#personas-recepcion)) y **no cambia**, aunque cambies la persona por defecto. * La bandeja ([`GET /mailbox`](https://docs.matias-api.com/docs/endpoints/events-radian#buzon-recepcion)) muestra los **correos**, incluidos los que no generaron documento, con el motivo. * El consumo del buzón usa un **cupo de recepción** aparte del de emisión ([`GET /mailbox/quota`](https://docs.matias-api.com/docs/endpoints/events-radian#buzon-recepcion)). ::: ### Cómo se procesa cada correo * Se leen los **ZIP** y los **XML sueltos** del correo, hasta **10 documentos por correo**. Cada **ZIP** aporta un solo documento. * 🆕 **Destinatarios:** el proveedor puede poner el buzón en **Para** o en **CC**, junto con la dirección del cliente. Si un mismo correo trae **varias direcciones de buzón** de la plataforma, solo se entrega a la **primera**; los demás buzones no lo reciben ni lo ven en su bandeja. El proveedor debe enviar **un correo por empresa**. * 🆕 **Cupo solo para documentos nuevos:** consume cupo de recepción solo un documento cuyo CUFE la empresa **no tenía** registrado. Si el CUFE ya existe (llegó antes por el buzón, el proveedor lo reenvió o se importó por Excel o por CUFE), se reutiliza ese documento: **no consume cupo**, no queda `FAILED` por falta de cupo y solo se crean los eventos automáticos que aún no tenga, según el [modo de la empresa](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos). Si el documento importado no tenía archivos, se le agregan los del correo (el XML y el ZIP, o el AttachedDocument si llegó suelto). El correo queda `PROCESSED` y vinculado a ese documento. * Es **idempotente por CUFE**: un correo repetido o un reintento no duplica documentos, ni cobra cupo otra vez, ni duplica eventos. * 🆕 **Numeración de eventos solo si hay eventos que crear:** el buzón exige una numeración activa de eventos de recepción solo para una **factura de venta a crédito** en **modo automático** (`send_events` = `true`) a la que le faltan eventos `030`, `032` o `033`. Las facturas de contado (o sin medio de pago), las notas crédito/débito y cualquier factura en modo manual quedan `PROCESSED` aunque la empresa no tenga numeración (ver la fila `FAILED` correspondiente abajo). * Los correos se conservan **30 días** desde su llegada para poder reintentarlos. **🆕 Adjuntos que se reconocen:** * Nombres de adjunto codificados (RFC 2047 y RFC 2231). Un adjunto **sin nombre** o con la **extensión equivocada** se reconoce por su contenido: el ZIP por su firma y el XML por su contenido, con o sin BOM. * El `.xml` adjunto directo, el ZIP con **subcarpetas** y un **ZIP dentro del ZIP** (un solo nivel de anidamiento). Dentro del ZIP se ignoran `__MACOSX`, los archivos ocultos y los PDF. * De un **AttachedDocument** se extrae la factura o nota embebida, venga en CDATA o escapada. * Un XML que no es factura, nota ni AttachedDocument (por ejemplo, un ApplicationResponse) **no cuenta** como documento. * Un correo reenviado **como adjunto** (`.eml`) no se abre: el proveedor debe adjuntar el ZIP o el XML directamente. * Por seguridad hay topes de tamaño y de número de entradas, también para el ZIP interno. Se descartan las rutas con `..` y los XML con DOCTYPE o entidades. Motivos de descarte de un documento del correo: | Motivo (`error_message`) | |---| | `Documento descartado: el XML declara DOCTYPE o entidades, que la DIAN no usa` | | `Documento descartado: el XML adjunto no está bien formado` | | `Documento descartado: el AttachedDocument adjunto no trae la factura o nota embebida` | | `El .zip adjunto no contiene ningún XML` | | `Tipo de documento 'X' aún no soportado por el buzón` (el buzón recibe facturas `01` y notas `91` y `92`) | **🆕 Correos con varios documentos:** * Cada documento se procesa con las mismas reglas (adquiriente, cupo, eventos) y uno inválido **no bloquea** a los demás. * `document_reception` de la bandeja enlaza solo el **primer documento procesado**; los demás se consultan en [`GET /document-receptions`](https://docs.matias-api.com/docs/endpoints/events-radian#consultas). * Si algún documento quedó **sin cupo** o **con error**, el correo queda `FAILED` aunque otros se hayan procesado. Su reintento no duplica ni vuelve a cobrar los ya creados. * Los documentos que pasan del tope de **10** se **omiten**. Ningún reintento los recupera (el reintento vuelve a leer los mismos 10 primeros): hay que enviarlos en otro correo. * Si todos los documentos se descartan, el correo queda `IGNORED`. * `error_message` trae un resumen cuando algún documento quedó pendiente, se omitió o se descartó, también si el estado es `PROCESSED` (máximo 250 caracteres): `N de M documentos del correo procesados`, seguido, según el caso, de `; X pendiente(s) de reintento: motivo`, `; X omitido(s): el máximo es 10 documentos por correo` y `; X descartado(s): motivo`. Si todos se procesaron, es `null`. **Estados del correo (`status`) y motivos (`error_message`):** | `status` | Motivo | ¿Se puede reintentar? | |---|---|---| | `PROCESSED` | Generó su documento (`document_reception`) | No hace falta | | `PENDING` | En cola o procesándose | No (ya está en proceso) | | `IGNORED` | Correo descartado por **virus** | No | | `IGNORED` | `El plan no incluye buzón de recepción` (plan FREE, o ni la empresa ni su Casa de Software tienen un plan de pago vencido o reemplazado: por ejemplo, nunca pagaron o su suscripción de pago fue **cancelada**) | No | | `IGNORED` + `reason_code` `PLAN_EXPIRED` (**retenido**, `is_held` = `true`) | `Retenido: el plan de pago venció. Se procesará automáticamente al renovar el plan, si no han pasado más de 30 días desde su llegada`. No consume cupo. Ver [Correos retenidos](https://docs.matias-api.com/docs/endpoints/events-radian#correos-retenidos). | Sí: se procesa solo al renovar el plan, o con `POST /mailbox/{id}/retry` | | `IGNORED` | `El correo no trae ningún adjunto .zip` (no trae ZIP ni XML de factura) | No | | `IGNORED` | `Documento descartado: el adquiriente (NIT X) no es esta empresa (NIT Y)` o `Documento descartado: el XML no trae el NIT del adquiriente y no se puede verificar que sea para esta empresa`. Se avisa por correo a la cuenta principal o a la Casa de Software. 🆕 El NIT se compara solo por sus dígitos: se descarta el DV si viene tras un guion y se quitan puntos, espacios y ceros a la izquierda; también coincide si el NIT de la empresa está guardado con el DV pegado al final. Una diferencia de formato no descarta el documento. | No | | `IGNORED` | Correo o ZIP demasiado grande (el motivo indica el límite) | No | | `FAILED` | `Sin cupo disponible en el plan del buzón de recepción` (solo para un documento nuevo) | Sí, al ampliar el cupo | | `FAILED` | 🆕 `No tiene una numeración activa para los eventos de recepción`: modo automático y factura a crédito sin numeración activa de eventos. El documento **sí** queda registrado (aparece en `document_reception`). | Sí, tras crear la numeración: el reintento encuentra el documento por CUFE, no lo duplica ni cobra cupo otra vez, y crea los eventos | | `FAILED` | Error al procesar el documento | Sí | | `FAILED` + `reason_code` `S3_OBJECT_MISSING` | El correo ya no está almacenado | No | **🆕 Correos retenidos por plan vencido:** * Un correo se retiene solo si la empresa o su Casa de Software **tuvo** un plan de pago que venció o que se reemplazó por un cambio de plan. Una suscripción de pago **cancelada** no cuenta: el correo queda `IGNORED` con `El plan no incluye buzón de recepción` y no se retiene. * Mientras haya retenidos, la Casa de Software (o la cuenta principal, si la empresa no es sub-cuenta) recibe como máximo **un correo al día por empresa**: «Tienes N facturas retenidas en el buzón porque tu plan venció. Renueva para procesarlas.». La sub-cuenta nunca recibe ese aviso. * Al activarse un plan de pago (compra, renovación, upgrade o asignación manual), los retenidos de los **últimos 30 días** se procesan solos y vuelven a validar el cupo: si no hay cupo, quedan `FAILED` con `Sin cupo disponible en el plan del buzón de recepción`. **GET /mailbox — Bandeja del Buzón** Lista los **correos** que llegaron al buzón de la empresa, del más reciente al más antiguo. **Query Params:** | Parámetro | Tipo | Default | Descripción | |-------|------|---------|-------------| | `status` | `string` | — | `PENDING`, `PROCESSED`, `FAILED` o `IGNORED` | | `query` | `string` | — | Busca en el asunto y en el remitente (`%` y `_` se buscan como texto literal) | | `startDate` | `string` | — | Fecha inicio (`YYYY-MM-DD`) de recepción del correo | | `endDate` | `string` | — | Fecha fin (`YYYY-MM-DD`) de recepción; incluye todo el día | | `archived` | `boolean` | `false` | `1` muestra solo los archivados; por defecto, solo los no archivados. Hoy ningún endpoint archiva correos, así que `archived=1` devuelve una lista vacía. | | `limit` | `int` | 20 | Registros por página (máx. 50) | | `client_uuid` | `string` | — | UUID del cliente (Multi-Tenant) | **cURL** ```bash curl -X GET "{{url}}/events/mailbox?status=FAILED&limit=20" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/mailbox`, { params: { status: 'FAILED', limit: 20 }, headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/mailbox", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'query' => ['status' => 'FAILED', 'limit' => 20], ]); ``` **💻 Ver Respuesta Exitosa (200)** `dataRecords` es un paginador (`current_page`, `data`, `per_page`, `total`, etc.). ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 3105, "from_email": "facturacion@proveedor-ejemplo.com", "subject": "Factura electrónica FE1001", "status": "PROCESSED", "received_at": "2026-08-15T14:18:00.000000Z", "has_attachments": true, "read_at": null, "archived_at": null, "starred": false, "error_message": null, "reason_code": null, "is_held": false, "can_retry": false, "document_reception": { "id": 42, "folio": "FE1001", "total": "1190000.00", "issue_date": "2026-08-15T00:00:00.000000Z" } }, { "id": 3106, "from_email": "facturacion@proveedor-ejemplo.com", "subject": "Factura electrónica FE1002", "status": "FAILED", "received_at": "2026-08-16T10:02:00.000000Z", "has_attachments": true, "read_at": null, "archived_at": null, "starred": false, "error_message": "Sin cupo disponible en el plan del buzón de recepción", "reason_code": null, "is_held": false, "can_retry": true, "document_reception": null } ], "per_page": 20, "total": 2 }, "success": true } ``` | Campo | Descripción | |-------|-------------| | `received_at` | Fecha de la cabecera del correo o, si falta, la del procesamiento. El listado se ordena por ella. | | `read_at`, `archived_at`, `starred` | 🆕 De **solo lectura**: ningún endpoint del API marca un correo como leído, archivado o destacado | | `error_message` | Motivo legible por el que el correo no generó documento, o el resumen de un correo con varios documentos (`null` si no aplica) | | `reason_code` | `PLAN_EXPIRED` (retenido por plan vencido), `S3_OBJECT_MISSING` (el correo ya no está almacenado) o `null` | | `is_held` | `true` si el correo está retenido por plan vencido | | `can_retry` | `true` si `POST /mailbox/{id}/retry` lo aceptaría | | `document_reception` | Documento generado (`id`, `folio`, `total`, `issue_date`) o `null`. Con varios documentos, el primero procesado. | **GET /mailbox/address — Dirección del Buzón** Devuelve la dirección a la que los proveedores deben enviar las facturas. Es de **solo lectura**. **cURL** ```bash curl -X GET "{{url}}/events/mailbox/address" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/mailbox/address`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/mailbox/address", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** El dominio del ejemplo es ilustrativo. ```json { "dataRecord": { "address": "abc123xyz@buzon.ejemplo.com", "local_part": "abc123xyz", "domain": "buzon.ejemplo.com", "provisioned": true, "reception_person_uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "message": null }, "success": true } ``` * El identificador es de la **empresa** (uno solo) y no cambia al cambiar la persona por defecto. * Si la empresa eliminó su última persona de recepción, la dirección se conserva (sigue recibiendo correo) y `reception_person_uuid` es `null`. Si la empresa **aún no tiene buzón**, la respuesta también es `200`: ```json { "dataRecord": { "address": null, "local_part": null, "domain": "buzon.ejemplo.com", "provisioned": false, "reception_person_uuid": null, "message": "La empresa aún no tiene buzón de recepción: registre la persona de recepción en la configuración de eventos para obtener la dirección." }, "success": true } ``` **GET /mailbox/quota — Cupo de Recepción** Consumo del **cupo de recepción** de la suscripción vigente (contador aparte del de emisión). Si la empresa es sub-cuenta, el cupo es la bolsa de su Casa de Software. **cURL** ```bash curl -X GET "{{url}}/events/mailbox/quota" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/mailbox/quota`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/mailbox/quota", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "dataRecord": { "unlimited": false, "used": 850, "limit": 1000, "remaining": 150, "percent": 85, "exhausted": false, "level": "WARNING", "shared_pool": false, "period_start": "2026-01-01", "period_end": "2026-12-31", "message": "Ya usaste más del 80 % de tu cupo de recepción.", "upgrade_route": "/membership/subscription" }, "success": true } ``` | Campo | Descripción | |-------|-------------| | `unlimited` | `true` en planes ilimitados (`used`, `limit`, `remaining` y `percent` pueden venir en `null`) | | `percent` | `used × 100 / limit`, con 2 decimales y sin tope (puede pasar de 100). `100` si `limit` es `0`; `null` si es ilimitado. | | `exhausted` | `true` si `used` ≥ `limit` | | `level` | `OK`, `WARNING` (más de 80 %), `CRITICAL` (más de 95 %), `EXHAUSTED` o `UNLIMITED` | | `shared_pool` | `true` si es una sub-cuenta que consume la bolsa de su Casa de Software | | `message` | Aviso según el nivel (`null` en `OK` y `UNLIMITED`) | | `upgrade_route` | Ruta de la pantalla para ampliar o renovar el plan | **Mensajes por nivel:** | `level` | `message` | |---|---| | `WARNING` | `Ya usaste más del 80 % de tu cupo de recepción.` | | `CRITICAL` | `Tu cupo de recepción está por agotarse: cuando se acabe, los correos nuevos quedarán sin procesar.` | | `EXHAUSTED` | `Tu cupo de recepción se agotó: los correos nuevos quedarán sin procesar hasta que amplíes tu plan.` | :::note Cupo compartido Con `shared_pool` = `true`, cuando consulta la **propia sub-cuenta** (con o sin su `client_uuid`), `used`, `limit` y `remaining` vienen en `null`: solo ve el porcentaje y el nivel. La Casa de Software que consulta con `client_uuid` ve todas las cifras. Sin cupo, los correos nuevos quedan `FAILED` con `Sin cupo disponible en el plan del buzón de recepción`. ::: **GET /mailbox/{id} — Detalle de un Correo** **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `id` | `int` | ID del correo (campo `id` de `GET /mailbox`) | Devuelve los mismos campos del listado más `to_email`, `message_id`, `body_preview`, `processed_at` y `files`. Para descargar un archivo usa [`GET /document-receptions/{documentId}/files/{fileManagerId}/download`](https://docs.matias-api.com/docs/endpoints/events-radian#consultas) con `document_reception.id` y `files[].id`. **cURL** ```bash curl -X GET "{{url}}/events/mailbox/3105" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/mailbox/${emailId}`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/mailbox/{$emailId}", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** ```json { "dataRecord": { "id": 3105, "from_email": "facturacion@proveedor-ejemplo.com", "subject": "Factura electrónica FE1001", "status": "PROCESSED", "received_at": "2026-08-15T14:18:00.000000Z", "has_attachments": true, "read_at": null, "archived_at": null, "starred": false, "error_message": null, "reason_code": null, "is_held": false, "can_retry": false, "document_reception": { "id": 42, "folio": "FE1001", "total": "1190000.00", "issue_date": "2026-08-15T00:00:00.000000Z" }, "to_email": "abc123xyz@buzon.ejemplo.com", "message_id": "0100018f-ejemplo-mensaje", "body_preview": "Adjuntamos la factura electrónica FE1001.", "processed_at": "2026-08-15T14:20:00.000000Z", "files": [ { "id": 9001, "type_document": "XML", "file_name": "document.xml", "mime_type": "application/xml", "file_size": 18432 } ] }, "success": true } ``` `files[].type_document` puede ser `INBOUND_EMAIL_RAW`, `XML`, `ZIP` o `ATTACHED_DOC`. `files` está vacío si el correo no generó documento. **❌ Correo no encontrado (HTTP 404)** Si el correo no existe o es de otra empresa: ```json { "success": false, "message": "Correo no encontrado en el buzón." } ``` **POST /mailbox/{id}/retry — Reintentar un Correo** Vuelve a poner en cola un correo `FAILED` o **retenido** (`IGNORED` con `reason_code` `PLAN_EXPIRED`). El reproceso revalida todo (plan, cupo, NIT del adquiriente) y el resultado se ve en la bandeja. El campo `can_retry` de `GET /mailbox` indica de antemano si se acepta. **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `id` | `int` | ID del correo | **cURL** ```bash curl -X POST "{{url}}/events/mailbox/3106/retry" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/mailbox/${emailId}/retry`, {}, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/mailbox/{$emailId}/retry", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (202)** ```json { "success": true, "message": "El correo se volvió a poner en cola para procesarlo.", "dataRecord": { "id": 3106, "status": "PENDING" } } ``` **❌ Ver Respuestas de Error** Cuerpo: `{"success": false, "message": "...", "error_code": "..."}` (el `404` no trae `error_code`). | HTTP | `error_code` | Mensaje | |:---:|---|---| | `404` | — | `Correo no encontrado en el buzón.` (no existe, es de otra empresa o no correspondía a ningún buzón) | | `409` | `MAILBOX_ALREADY_PROCESSED` | `El correo ya se procesó y generó su documento.` | | `409` | `MAILBOX_RETRY_IN_PROGRESS` | `El correo ya se está procesando.` (está `PENDING`) o `El correo ya se está reprocesando.` (otro reintento lo tomó; no se encola otra vez) | | `409` | `MAILBOX_RETRY_NO_LOCATION` | `Este correo llegó antes de que se guardara su ubicación: no se puede reintentar. Pida al proveedor que lo envíe de nuevo.` o `La ubicación guardada del correo ya no corresponde al buzón de entrada configurado: no se puede reintentar.` | | `409` | `MAILBOX_RETRY_EXPIRED` | `El correo superó los 30 días de retención: ya no se puede reintentar. Pida al proveedor que lo envíe de nuevo.` o `El correo ya no está disponible en el almacenamiento: pida al proveedor que lo envíe de nuevo.` | | `409` | `MAILBOX_RETRY_UNAVAILABLE` | `El reintento aún no está habilitado: el servidor no guarda la ubicación de los correos.` | | `422` | `MAILBOX_RETRY_NOT_RETRYABLE` | `Los correos ignorados (virus, factura de otro NIT, sin XML o demasiado grandes) no se reintentan: pida al proveedor que lo envíe de nuevo.` | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | --- ## 👤 8. Personas de Recepción :::info 🆕 Nuevo · Quién firma los eventos Las **personas de recepción** son quienes firman (acusan, reciben, aceptan o reclaman) los eventos RADIAN de la empresa. Una empresa puede tener **varias**, con **una por defecto**: la que firma los eventos automáticos y la que se usa cuando no envías `reception_person_uuid`. * La **primera** persona nace por defecto y recibe el [buzón de recepción](https://docs.matias-api.com/docs/endpoints/events-radian#buzon-recepcion). * El [modo de eventos](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos) (`send_events`) es de la **empresa**, no de una persona: se cambia con [`PUT /reception-people/mode`](https://docs.matias-api.com/docs/endpoints/events-radian#modo-eventos). * Todos estos endpoints aceptan `client_uuid` para Casas de Software. ::: :::warning El CRUD genérico ya no escribe personas de recepción Las personas de recepción (`tbPrefix=T005`) son ahora de **solo lectura** por el CRUD genérico (`{{url}}/crud?tbPrefix=T005`): crear, editar o eliminar por esa vía responde `403` indicando que se gestionan con `/api/ubl2.1/events/reception-people`. La lectura excluye las personas eliminadas y, sin plan de pago vigente, cualquier operación (también la lectura) responde `403` con `error_code` `RECEPTION_NOT_IN_PLAN`. Si registrabas la persona de recepción por el CRUD, migra a los endpoints de esta sección. ::: **Objeto `ReceptionPerson`:** | Campo | Tipo | Descripción | |-------|------|-------------| | `uuid` | `string` | Identificador de la persona (se usa en las rutas y en `reception_person_uuid`) | | `identity_document_id` | `int` \| `null` | Tipo de documento de identidad | | `identity_document` | `object` \| `null` | `id`, `code` y `name` del tipo de documento | | `dni` | `string` | Número de identificación | | `dv` | `string` \| `null` | Dígito de verificación | | `first_name` | `string` | Nombres | | `last_name` | `string` | Apellidos | | `email` | `string` \| `null` | Correo de la persona | | `job_title` | `string` \| `null` | Cargo | | `department` | `string` \| `null` | Área o departamento | | `is_default` | `boolean` | `true` si es la persona por defecto | | `created_at` / `updated_at` | `string` | Fechas en ISO 8601 | La dirección del buzón no se incluye: se consulta con [`GET /mailbox/address`](https://docs.matias-api.com/docs/endpoints/events-radian#buzon-recepcion). **Estado de la empresa** (lo devuelven `GET /reception-people`, `DELETE`, `POST .../default` y `PUT /mode` en `dataRecord`): | Campo | Tipo | Descripción | |-------|------|-------------| | `send_events` | `boolean` | Modo de la empresa: `true` automático, `false` manual (`false` si no hay personas) | | `default_person_uuid` | `string` \| `null` | `uuid` de la persona por defecto | | `people` | `array` | Lista de `ReceptionPerson`, con la persona por defecto primero | **GET /reception-people — Listar Personas y Modo** **cURL** ```bash curl -X GET "{{url}}/events/reception-people" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.get(`${url}/events/reception-people`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->get("{$url}/events/reception-people", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** `people` es una lista vacía si la empresa no tiene personas. ```json { "dataRecord": { "send_events": false, "default_person_uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "people": [ { "uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "identity_document_id": 1, "identity_document": { "id": 1, "code": "13", "name": "Cédula de ciudadanía" }, "dni": "1234567890", "dv": null, "first_name": "Ana", "last_name": "Pérez", "email": "recepcion@empresa-ejemplo.com", "job_title": "Analista de cuentas por pagar", "department": "Contabilidad", "is_default": true, "created_at": "2026-10-01T15:00:00+00:00", "updated_at": "2026-10-01T15:00:00+00:00" } ] }, "success": true } ``` **POST /reception-people — Registrar Persona** **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `identity_document_id` | `integer` | **Sí** | Tipo de documento de identidad; debe existir en `GET {{url}}/identity-documents` | | `dni` | `string` (máx. 20) | **Sí** | Número de identificación | | `dv` | `string` (máx. 2) | No | Dígito de verificación | | `first_name` | `string` (máx. 60) | **Sí** | Nombres | | `last_name` | `string` (máx. 60) | **Sí** | Apellidos | | `email` | `string` (email, máx. 100) | No | Correo de la persona | | `job_title` | `string` (máx. 100) | No | Cargo | | `department` | `string` (máx. 100) | No | Área o departamento | :::caution Campos que no se envían Estos campos responden `422` con el endpoint correcto en el mensaje: `is_default` (usa `POST /reception-people/{uuid}/default`), `send_events` (usa `PUT /reception-people/mode`), `email_reception` (la dirección del buzón la asigna el servidor) y `company_id` (la empresa sale del token o de `client_uuid`). `uuid` e `id` se ignoran. ::: **Reglas:** * La **primera** persona de la empresa nace por defecto (`is_default` = `true`) y recibe el buzón: la misma dirección si la empresa ya tuvo una, o una nueva. * Una empresa nueva arranca en **modo manual** (`send_events` = `false`). Si la empresa ya había tenido personas y las eliminó, la nueva primera persona recupera el modo anterior. * Las siguientes personas no son por defecto, no generan buzón y heredan el modo de la empresa. **cURL** ```bash curl -X POST "{{url}}/events/reception-people" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "identity_document_id": 1, "dni": "1234567890", "first_name": "Ana", "last_name": "Pérez", "email": "recepcion@empresa-ejemplo.com", "job_title": "Analista de cuentas por pagar", "department": "Contabilidad" }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/reception-people`, { identity_document_id: 1, dni: '1234567890', first_name: 'Ana', last_name: 'Pérez', email: 'recepcion@empresa-ejemplo.com', job_title: 'Analista de cuentas por pagar', department: 'Contabilidad' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/reception-people", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'identity_document_id' => 1, 'dni' => '1234567890', 'first_name' => 'Ana', 'last_name' => 'Pérez', 'email' => 'recepcion@empresa-ejemplo.com', 'job_title' => 'Analista de cuentas por pagar', 'department' => 'Contabilidad', ], ]); ``` **💻 Ver Respuesta Exitosa (201)** ```json { "message": "Persona de recepción registrada.", "dataRecord": { "uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "identity_document_id": 1, "identity_document": { "id": 1, "code": "13", "name": "Cédula de ciudadanía" }, "dni": "1234567890", "dv": null, "first_name": "Ana", "last_name": "Pérez", "email": "recepcion@empresa-ejemplo.com", "job_title": "Analista de cuentas por pagar", "department": "Contabilidad", "is_default": true, "created_at": "2026-10-01T15:00:00+00:00", "updated_at": "2026-10-01T15:00:00+00:00" }, "success": true } ``` **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | | `422` | `El tipo de documento de identidad no existe.` | `identity_document_id` fuera del catálogo | | `422` | `is_default no se envía: use POST /events/reception-people/{uuid}/default.` | Se envió `is_default` | | `422` | `send_events es de la empresa: use PUT /events/reception-people/mode.` | Se envió `send_events` | | `422` | `email_reception lo asigna el servidor: es la dirección del buzón de la empresa y no cambia.` | Se envió `email_reception` | | `422` | `company_id no se envía: la empresa sale del token o de client_uuid.` | Se envió `company_id` | Los `422` siguen el formato `{"message": "...", "errors": {"campo": ["..."]}}`. **PUT /reception-people/{uuid} — Editar Persona** Edición **parcial**: cada campo es opcional, pero si se envía debe ser válido (mismas reglas y mismos campos prohibidos que el registro). No cambia la persona por defecto, el buzón ni el modo. Los eventos ya emitidos no cambian. **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `uuid` | `string` (uuid) | `uuid` de la persona | **cURL** ```bash curl -X PUT "{{url}}/events/reception-people/9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "job_title": "Jefe de cuentas por pagar" }' ``` **JavaScript (Axios)** ```js const response = await axios.put(`${url}/events/reception-people/${personUuid}`, { job_title: 'Jefe de cuentas por pagar' }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->put("{$url}/events/reception-people/{$personUuid}", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => ['job_title' => 'Jefe de cuentas por pagar'], ]); ``` **💻 Ver Respuesta Exitosa (200)** `dataRecord` es el objeto `ReceptionPerson` actualizado. ```json { "message": "Persona de recepción actualizada.", "dataRecord": { "uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "identity_document_id": 1, "identity_document": { "id": 1, "code": "13", "name": "Cédula de ciudadanía" }, "dni": "1234567890", "dv": null, "first_name": "Ana", "last_name": "Pérez", "email": "recepcion@empresa-ejemplo.com", "job_title": "Jefe de cuentas por pagar", "department": "Contabilidad", "is_default": true, "created_at": "2026-10-01T15:00:00+00:00", "updated_at": "2026-10-05T10:00:00+00:00" }, "success": true } ``` **❌ Persona no encontrada (HTTP 404)** Si no existe, fue eliminada o es de otra empresa (misma respuesta): ```json { "success": false, "message": "La persona de recepción no existe para esta empresa." } ``` **DELETE /reception-people/{uuid} — Eliminar Persona** **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `uuid` | `string` (uuid) | `uuid` de la persona | :::caution Reglas de eliminación * No se puede eliminar la persona **por defecto** mientras haya otras registradas (`409`): primero marca otra como por defecto. * La **última** persona sí se puede eliminar. La dirección del buzón se conserva y sigue recibiendo correo (en `GET /mailbox/address`, `reception_person_uuid` pasa a `null`). Sin persona, el buzón se comporta como modo manual y la importación por Excel/CUFE y el envío de eventos responden `400` `No tiene una persona de recepción asignada`. * Al registrar la siguiente persona, la empresa recupera el mismo buzón y el modo anterior. ::: **cURL** ```bash curl -X DELETE "{{url}}/events/reception-people/9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.delete(`${url}/events/reception-people/${personUuid}`, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->delete("{$url}/events/reception-people/{$personUuid}", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** `dataRecord` es el estado resultante de la empresa. ```json { "message": "Persona de recepción eliminada.", "dataRecord": { "send_events": false, "default_person_uuid": null, "people": [] }, "success": true } ``` **❌ Ver Respuestas de Error** | HTTP | Mensaje | |:---:|---| | `404` | `La persona de recepción no existe para esta empresa.` | | `409` | `No se puede eliminar la persona de recepción por defecto mientras haya otras registradas. Marque otra como persona por defecto y vuelva a intentarlo.` | | `403` | `RECEPTION_NOT_IN_PLAN` (ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan)) | **POST /reception-people/{uuid}/default — Marcar Persona por Defecto** Marca la persona por defecto, sin body. El buzón (misma dirección) y el modo automático/manual se trasladan de la anterior y **no cambian**; si la empresa aún no tenía buzón, se le asigna uno a la nueva persona por defecto. Es **idempotente**: si ya es la persona por defecto, no cambia nada. **Path Params:** | Parámetro | Tipo | Descripción | |-------|------|-------------| | `uuid` | `string` (uuid) | `uuid` de la persona | **cURL** ```bash curl -X POST "{{url}}/events/reception-people/9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d/default" \ -H "Authorization: Bearer {token}" ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/events/reception-people/${personUuid}/default`, {}, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/events/reception-people/{$personUuid}/default", [ 'headers' => ['Authorization' => "Bearer {$token}"], ]); ``` **💻 Ver Respuesta Exitosa (200)** `dataRecord` es el estado resultante de la empresa (`people` con el mismo formato que en `GET /reception-people`). ```json { "message": "Persona de recepción por defecto actualizada.", "dataRecord": { "send_events": true, "default_person_uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "people": [] }, "success": true } ``` **❌ Persona no encontrada (HTTP 404)** ```json { "success": false, "message": "La persona de recepción no existe para esta empresa." } ``` **PUT /reception-people/mode — Modo de Eventos (Automático / Manual)** Cambia el [modo de eventos](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos) de la **empresa** (se aplica a todas sus personas). **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `send_events` | `boolean` | **Sí** | `true`: automático (se crean y envían `030`, `032` y `033` para facturas a crédito). `false`: manual (no se crea ningún evento automático, tampoco el `030`). | | `client_uuid` | `string` | No | UUID de la empresa cliente (Casa de Software) | :::warning El 033 automático En modo automático, la aceptación expresa (`033`) se envía sin intervención del usuario y quita la posibilidad de reclamar la factura con el `031`. Actívalo a conciencia. ::: **cURL** ```bash curl -X PUT "{{url}}/events/reception-people/mode" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "send_events": false }' ``` **JavaScript (Axios)** ```js const response = await axios.put(`${url}/events/reception-people/mode`, { send_events: false }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->put("{$url}/events/reception-people/mode", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => ['send_events' => false], ]); ``` **💻 Ver Respuesta Exitosa (200)** `dataRecord` es el estado resultante de la empresa (`people` con el mismo formato que en `GET /reception-people`). ```json { "message": "Modo de eventos actualizado.", "dataRecord": { "send_events": false, "default_person_uuid": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d", "people": [] }, "success": true } ``` **❌ Ver Respuestas de Error** | HTTP | Mensaje | Causa | |:---:|---|---| | `409` | `Registre primero una persona de recepción: el modo de los eventos se guarda con la persona por defecto.` | La empresa no tiene personas de recepción | | `422` | `Indique send_events: true (eventos automáticos) o false (manuales).` | Falta `send_events` | | `422` | `send_events debe ser true o false.` | Valor no booleano | | `403` | `RECEPTION_NOT_IN_PLAN` | Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan) | --- # 🚀 API de Autoincremento :::warning Autenticación Requerida Incluir en todos los endpoints: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Todos los endpoints aceptan `?client_uuid={{client_uuid}}` para operar en nombre de empresas cliente. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: Esta API delega a la plataforma el manejo del consecutivo y prefijo de los documentos, evitando colisiones en envíos concurrentes. :::note ¿Cómo funciona el autoincremento? Cuando envías una petición, la plataforma **reserva atómicamente** el siguiente número consecutivo disponible en la resolución DIAN indicada antes de procesar el documento. Así dos peticiones simultáneas nunca reciben el mismo número. **El body del payload es idéntico** al de los endpoints estándar (ej. `POST /invoice`), con una diferencia: **no envíes** `document_number`, que se asigna automáticamente. ::: #### Requisitos del body | Campo | ¿Se envía? | Descripción | |---|---|---| | `resolution_number` | ✅ Sí | Junto con `prefix` identifica la resolución. Debe ser una resolución de la empresa, activa y vigente hoy (entre su fecha inicial y su fecha final). | | `prefix` | ✅ Sí | Prefijo de esa resolución. Omítelo solo si la resolución no tiene prefijo. | | `document_number` | ❌ No | Si lo envías, la petición se rechaza con HTTP `422`. | | `uuid` | Opcional | Identificador del documento de tu lado (se recomienda un UUID). Evita que un reintento consuma otro consecutivo. Ver [UUID ya usado](https://docs.matias-api.com/docs/endpoints/autoincrement#uuid-ya-usado). | :::caution La numeración puede tener huecos Si la emisión falla **antes** de transmitir a la DIAN, la reserva se libera, pero el número solo se reutiliza si seguía siendo el último reservado de la resolución. Si la emisión falla **después** de iniciar la transmisión, el número se conserva para ese documento: consulta su estado y corrígelo con el `PATCH` de [Reenvío y Reintentos](https://docs.matias-api.com/docs/endpoints/autoincrement#reenvio) en lugar de enviar un `POST` nuevo. ::: **❌ Errores propios del autoincremento** **HTTP 422** — se envió `document_number`: ```json { "success": false, "message": "Para la generación automática, no debe enviar el campo document_number." } ``` **HTTP 404** — no hay una resolución activa y vigente con ese `resolution_number` y `prefix`: ```json { "success": false, "message": "No se encontró una resolución de facturación activa para los datos proporcionados." } ``` **HTTP 409** — el siguiente número supera el rango autorizado de la resolución (`Se ha alcanzado el número máximo para la resolución ...`). Solicita una nueva resolución a la DIAN. Además aplican los errores y advertencias comunes de la emisión (`409 DOCUMENT_IN_TRANSMISSION`, `409 DOCUMENT_IN_VALIDATION`, `503 DIAN_STATUS_UNAVAILABLE`, `DOCUMENT_POSSIBLY_SENT`, `warnings`). Ver [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). #### UUID ya usado Si envías un `uuid` que ya está registrado para tu empresa, **no se emite un documento nuevo** (cada `POST` consumiría otro consecutivo) y la respuesta es HTTP `400`. El `message` depende del estado del envío original: | Estado del envío original | `message` | |---|---| | Validado por la DIAN | `El documento {número} con el UUID proporcionado ya fue validado por la DIAN. No lo reenvíe: cada llamada a este endpoint genera un nuevo consecutivo.` | | Sin respuesta definitiva de la DIAN | `El UUID proporcionado corresponde al documento {número}, que aún no tiene una respuesta definitiva de la DIAN. No lo reenvíe: consulte su estado con el uuid del envío.` | | Procesado y no validado | `El UUID proporcionado ya fue procesado como el documento {número} y no se encuentra validado. Para corregirlo sin consumir un nuevo consecutivo use PATCH /api/ubl2.1/auto-increment/{tipo}/{uuid del envío}.` | | Sin envío asociado | `El UUID proporcionado ya fue utilizado en un envío anterior. No lo reenvíe por este endpoint: cada llamada genera un nuevo consecutivo.` | **❌ HTTP 400 — UUID ya procesado y no validado** ```json { "message": "El UUID proporcionado ya fue procesado como el documento F1001 y no se encuentra validado. Para corregirlo sin consumir un nuevo consecutivo use PATCH /api/ubl2.1/auto-increment/invoices/0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.", "data": { "shipping_history_uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "document_uuid": "3d6f1a8e-2b4c-4f7d-9e0a-6c1b5d8f2a47", "company_id": 1, "document_number": "F1001", "is_valid": false, "XmlDocumentKey": "6142be764d365c08872933a58c67177b000764e24e4108b67e5aa0872d709ad52a23c7543ffcad42bdec5e9e726d7644" }, "success": false } ``` `data.shipping_history_uuid` es el `uuid` del envío que debes usar en el `PATCH`; `data.document_uuid` es el `uuid` que enviaste. `data` puede incluir otros campos del registro. --- ## 📤 Emisión Autoincremental **POST /auto-increment/invoices — Factura Autoincremental** ```http POST {{url}}/auto-increment/invoices?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Emite una factura tomando automáticamente el siguiente número consecutivo disponible. **cURL** ```bash curl -X POST "{{url}}/auto-increment/invoices" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/auto-increment/invoices`, payload, { headers: { Authorization: `Bearer ${token}` } }); const uuid = response.data.uuid; const cufe = response.data.XmlDocumentKey; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/auto-increment/invoices", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => $payload, ]); ``` **✅ Respuesta Exitosa — DIAN 200 OK** ```json { "uuid": "0b96bb6e-7dd0-11f0-ba9f-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "6142be764d365c08872933a58c67177b000764e24e4108b67e5aa0872d709ad52a23c7543ffcad42bdec5e9e726d7644", "response": { "ErrorMessage": {}, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura Electrónica F1001, ha sido autorizada." }, "XmlBase64Bytes": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4...", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500001001.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500001001.xml", "data": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4..." }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=6142be76...", "url": "https://sandbox-api.matias-api.com/qr/1/QR-0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.png", "path": "1/QR-0b96bb6e-7dd0-11f0-ba9f-f02f74cac485.png", "data": "iVBORw0KGgoAAAANSUhEUgAA..." }, "pdf": null, "warnings": [], "success": true } ``` Guarda el `uuid` de la respuesta: identifica el envío y es el que usas en el `PATCH` de reenvío. La respuesta, el rechazo DIAN (HTTP `200` con `IsValid: "false"`), la forma en habilitación y los `warnings` son los mismos de `POST /invoice`. Ver [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). **POST /auto-increment/credit-notes · /debit-notes — Notas Autoincrementales** ```http POST {{url}}/auto-increment/credit-notes?client_uuid={{client_uuid}} POST {{url}}/auto-increment/debit-notes?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Notas de crédito y débito con numeración automática asignada. **POST /auto-increment/support-documents — Documento Soporte Autoincremental** ```http POST {{url}}/auto-increment/support-documents?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Cubre tanto residentes como no residentes (se define en el payload). **POST /auto-increment/pos-documents — Documento POS Autoincremental** ```http POST {{url}}/auto-increment/pos-documents?client_uuid={{client_uuid}} POST {{url}}/auto-increment/debit-notes?client_uuid={{client_uuid}} POST {{url}}/auto-increment/credit-notes?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Para Notas POS usa la misma ruta general, enviando el `type_document_id` adecuado en el JSON. **POST /auto-increment/adjustment-notes — Nota de Ajuste Autoincremental** ```http POST {{url}}/auto-increment/adjustment-notes?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Nota de ajuste a Documento Soporte con numeración automática. --- ## 🔄 Reenvío y Reintentos **PATCH /auto-increment/invoices/{uuid} — Reenvío de Factura** ```http PATCH {{url}}/auto-increment/invoices/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Reintenta la emisión de una factura autoincremental que no quedó validada (por ejemplo, rechazada por la DIAN), con el JSON corregido en el body. **Reutiliza el número, la resolución, el prefijo y el consecutivo originales**: no consume un número nuevo. | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del **envío** (el que devolvió la emisión original). Debe pertenecer a la empresa del token (o a la indicada en `client_uuid`). | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **❌ Errores del reenvío** **HTTP 404** — el `uuid` no corresponde a un envío de la empresa: ```json { "success": false, "message": "No se encontró ningún envío con el UUID proporcionado." } ``` **HTTP 400** — el documento ya está validado ante la DIAN: `El documento (...) con numero ... ya se encuentra validado.` No lo reenvíes. Además aplican `409 DOCUMENT_IN_TRANSMISSION`, `409 DOCUMENT_IN_VALIDATION`, `503 DIAN_STATUS_UNAVAILABLE` y `DOCUMENT_POSSIBLY_SENT`. Ver [Códigos de error de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#errores-emision). Estas reglas aplican a todos los endpoints `PATCH /auto-increment/...` de esta sección. **PATCH /auto-increment/credit-notes/{uuid} · /debit-notes/{uuid} — Reenvío de Notas Crédito y Débito** ```http PATCH {{url}}/auto-increment/credit-notes/{uuid}?client_uuid={{client_uuid}} PATCH {{url}}/auto-increment/debit-notes/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Reenvía una nota crédito o débito autoincremental no validada, conservando su número original. | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del envío de la nota a reenviar. | **PATCH /auto-increment/adjustment-notes/{uuid} — Reenvío de Nota de Ajuste** ```http PATCH {{url}}/auto-increment/adjustment-notes/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Reenvía una nota de ajuste a Documento Soporte autoincremental no validada, conservando su número original. | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del envío de la nota de ajuste a reenviar. | **PATCH /auto-increment/support-documents/{uuid} — Reenvío de Documento Soporte** ```http PATCH {{url}}/auto-increment/support-documents/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del envío del documento soporte a reenviar. | **PATCH /auto-increment/pos-documents/{uuid} — Reenvío de Documento POS** ```http PATCH {{url}}/auto-increment/pos-documents/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del envío del documento POS a reenviar. | **POST /documents/{uuid}/resend — Reenviar un documento** ```http POST {{url}}/documents/{uuid}/resend?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Reenvía **un** documento de la empresa, identificado por el `uuid` del envío (no es un reenvío en lote). No lleva body: la API reconstruye el documento a partir de su historial, con el **mismo número, resolución y prefijo**, y fuerza `send_email: 1` y `graphic_representation: 1`. El reenvío pasa por el mismo flujo de emisión que `POST /invoice`, con su misma respuesta y sus `warnings`. Ver [Respuestas, advertencias y errores de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#respuestas-emision). | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del **envío** (el que devolvió la emisión). Debe pertenecer a la empresa del token (o a la indicada en `client_uuid`). | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | :::warning Un reenvío transmitido genera un CUFE/CUDE nuevo El documento reconstruido no conserva la fecha y la hora originales. Si el reenvío llega a transmitirse a la DIAN, el documento tiene un CUFE/CUDE distinto al del intento anterior. ::: :::caution Datos que el reenvío no reconstruye El documento reconstruido no incluye el bloque `health` del sector salud ni los datos adicionales (`additional_data`, `customer.extra_data` y `lines[].extra_data`). Si el documento los lleva, no uses este endpoint: envía de nuevo el JSON completo con el mismo número o, en numeración automática, corrígelo con `PATCH /auto-increment/*/{uuid}`. ::: **❌ Errores del reenvío** **HTTP 404** — el `uuid` no corresponde a un envío de la empresa: ```json { "success": false, "message": "No se encontró el envío con UUID: 0b96bb6e-7dd0-11f0-ba9f-f02f74cac485" } ``` **HTTP 400** — el documento ya está validado ante la DIAN: `El documento (...) con numero ... ya se encuentra validado.` No lo reenvíes. **HTTP 400** — el envío está en cola de procesamiento: `El documento (...) con numero ... se encuentra en cola de procesamiento.` Además aplican `409 DOCUMENT_IN_TRANSMISSION` y, en producción, la [consulta previa a la DIAN del intento anterior](https://docs.matias-api.com/docs/endpoints/invoices-pos#reintento-transmitido) (HTTP `200` con `ALREADY_AUTHORIZED_RECOVERED`, `409 DOCUMENT_IN_VALIDATION` o `503 DIAN_STATUS_UNAVAILABLE`), la [regla 90](https://docs.matias-api.com/docs/endpoints/invoices-pos#regla-90) y `DOCUMENT_POSSIBLY_SENT`. Ver [Códigos de error de emisión](https://docs.matias-api.com/docs/endpoints/invoices-pos#errores-emision). --- # 📊 Gestión de Membresías y Consumo :::warning Autenticación Requerida Incluir en todos los endpoints: `Authorization: Bearer {token}` ::: Esta sección abarca la consulta del plan y del consumo, las recargas de documentos, la gestión de cuotas de emisión para clientes y el uso de almacenamiento y correos del plan. Está pensada para casas de software, ERPs y plataformas SaaS integradoras. :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes consultar el resumen, las recargas y las estadísticas de una sub-cuenta propia y activa agregando `?client_uuid={{client_uuid}}`. El UUID se obtiene del listado de clientes: ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` En los endpoints de **cuotas** (`POST /memberships/quotas` y `DELETE /memberships/quotas/{client_uuid}`) el `client_uuid` identifica **al cliente que recibe la cuota**. Quien actúa es siempre la Casa de Software dueña del token. ::: --- ## 📈 Membresías y Consumo **GET /memberships/summary — Resumen de Membresía** ```http GET {{url}}/memberships/summary?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Obtiene el resumen de la membresía: tipo de cuenta, plan, límite de documentos, consumo, vigencia y, para la cuenta principal, las cuotas asignadas a los clientes (paginadas). **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID de una sub-cuenta propia y activa (Casa de Software). Debe ser el UUID, no el ID numérico. | | `search` | query | No | Filtra las cuotas asignadas por razón social del cliente (coincidencia parcial). | | `page` | query | No | Página del listado de cuotas asignadas (15 por página). | **Campos de `data`:** | Campo | Tipo | Descripción | |---|---|---| | `type` | `string` | `developer` (cuenta principal o independiente), `client_quota` (sub-cuenta con cuota asignada) o `client_shared` (sub-cuenta que consume de la bolsa de la Casa de Software). | | `plan_name` | `string` | Nombre del plan. En `client_quota` es `Cuota de {razón social de la Casa de Software}`; en `client_shared`, `Plan {plan} (Compartido)`. | | `is_unlimited` | `boolean` | `true` si el plan no tiene límite de documentos. | | `valid_until` | `string` | Fecha de vencimiento (`YYYY-MM-DD`). En `developer` es la fecha más lejana entre las suscripciones vigentes. | | `limit` | `integer` | Límite de documentos. En `developer` suma todas las suscripciones vigentes (incluida una renovación en cola); en `client_quota` es la cuota asignada; en `client_shared`, la bolsa de la Casa de Software. | | `consumed` | `integer` | Documentos consumidos. En `client_shared` es el consumo total de la Casa de Software. Ver [qué consume del plan](https://docs.matias-api.com/docs/endpoints/memberships-health#que-consume-del-plan). | | `assigned_quotas` | `object` | Solo en `developer`. Listado paginado de las cuotas asignadas a clientes. | | `daily_limit` | `object` | Solo en `developer` con plan **FREE**: `limit`, `consumed`, `remaining` y `next_reset` (inicio del día siguiente, ISO 8601). | :::note Formato de la respuesta Esta respuesta va envuelta en `data`, **sin** `dataRecords` y **sin** `success`. ::: :::info 🆕 Qué consume del plan Un documento consume del plan **una sola vez**: cuando la DIAN lo valida en la emisión (`response.IsValid: "true"`). Un evento RADIAN consume cuando la DIAN lo acepta. **No** consumen: - Los documentos rechazados o pendientes. - El documento que la API devuelve como ya autorizado, con `ALREADY_AUTHORIZED_RECOVERED` en `warnings` (tras la [regla 90](https://docs.matias-api.com/docs/endpoints/invoices-pos#regla-90) o en el [reintento de un número ya transmitido](https://docs.matias-api.com/docs/endpoints/invoices-pos#reintento-transmitido)), porque ya estaba emitido. - El documento que queda validado más tarde, al consultar su estado (`POST /status/document/{trackId}`) o en la revalidación automática. Por eso `consumed` puede ser menor que el número de documentos que reporta [`GET /documents/consume`](https://docs.matias-api.com/docs/endpoints/queries-status#busqueda-listado): en sus agrupaciones `p_type` `1` a `4` ese reporte cuenta documentos validados por la DIAN, no consumo cobrado, así que incluye los recuperados y los validados tarde, que no se cobran. Ver [Documento recuperado: sin cobro de consumo](https://docs.matias-api.com/docs/endpoints/invoices-pos#recuperado-sin-consumo). ::: **✅ Respuesta Exitosa — Cuenta principal (HTTP 200)** ```json { "data": { "type": "developer", "plan_name": "PLAN CASA DE SOFTWARE", "is_unlimited": false, "valid_until": "2027-10-13", "limit": 10000, "consumed": 3254, "assigned_quotas": { "current_page": 1, "data": [ { "id": 58, "uuid": "9b2f6d0e-3c1a-4f7e-8a51-2d6c0b9e4f13", "subscription_id": 311, "client_company_id": 1262, "assigned_documents": 500, "consumed_documents": 120, "start_date": "2026-10-14T05:00:00.000000Z", "end_date": "2027-10-13T05:00:00.000000Z", "created_at": "2026-10-14T20:15:03.000000Z", "updated_at": "2026-10-14T20:15:03.000000Z", "client": { "id": 1262, "company_name": "Cliente Ejemplo S.A.S" } } ], "first_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/memberships/summary?page=1", "from": 1, "last_page": 1, "last_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/memberships/summary?page=1", "links": [], "next_page_url": null, "path": "https://sandbox-api.matias-api.com/api/ubl2.1/memberships/summary", "per_page": 15, "prev_page_url": null, "to": 1, "total": 1 } } } ``` **✅ Respuesta Exitosa — Plan FREE (HTTP 200)** ```json { "data": { "type": "developer", "plan_name": "FREE", "is_unlimited": false, "valid_until": "2027-10-13", "limit": 100, "consumed": 42, "assigned_quotas": { "current_page": 1, "data": [], "per_page": 15, "total": 0 }, "daily_limit": { "limit": 10, "consumed": 3, "remaining": 7, "next_reset": "2026-10-22T00:00:00-05:00" } } } ``` **✅ Respuesta Exitosa — Sub-cuenta con cuota (HTTP 200)** ```json { "data": { "type": "client_quota", "plan_name": "Cuota de CASA DE SOFTWARE EJEMPLO S.A.S", "is_unlimited": false, "valid_until": "2027-10-13", "limit": 500, "consumed": 120 } } ``` **✅ Respuesta Exitosa — Sub-cuenta sin cuota (HTTP 200)** ```json { "data": { "type": "client_shared", "plan_name": "Plan PLAN CASA DE SOFTWARE (Compartido)", "is_unlimited": false, "valid_until": "2027-10-13", "limit": 10000, "consumed": 3254 } } ``` **❌ Sin membresía activa (HTTP 404)** En una sub-cuenta, también aplica si la Casa de Software no tiene membresía activa. ```json { "message": "No se encontró una membresía activa o legada para esta cuenta.", "data": null, "success": false } ``` **❌ client_uuid no autorizado (HTTP 403)** La empresa del `client_uuid` no es una sub-cuenta activa de quien llama. ```json { "success": false, "message": "Acceso no autorizado. Acceso denegado: No tiene permisos para operar sobre esta empresa o la relación no está activa." } ``` Si en lugar del UUID se envía el ID numérico de la empresa, la respuesta es `500` con el mensaje `Error interno del servidor. Debe enviar el UUID del cliente, no el ID.`. **GET /memberships/recharges — Mis Recargas** :::info 🆕 Nuevo Desglose de la suscripción activa entre los documentos del plan y los documentos recargados, con su vencimiento. ::: ```http GET {{url}}/memberships/recharges?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Muestra cuántos documentos del límite vienen del plan y cuántos de recargas, cuántos recargados quedan sin usar, cuándo vencen y el historial de recargas. Es de solo lectura y no incluye datos del pago (ni precio, ni monto, ni referencia). **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID de una sub-cuenta propia y activa. Se consulta la suscripción activa **propia** de esa empresa. | **Campos de la respuesta:** | Campo | Tipo | Descripción | |---|---|---| | `plan_documents` | `integer` | Tamaño del plan según el catálogo. Si el catálogo no lo define, es igual a `base_documents`. | | `base_documents` | `integer` | Documentos del límite que no vienen de recargas del ciclo vigente. `base_documents` + `recharged_documents` = `total_documents`. | | `recharged_documents` | `integer` | Documentos recargados en el ciclo vigente. | | `total_documents` | `integer` | Límite de documentos de la suscripción activa. | | `unused_recharged_documents` | `integer` | Recargados sin usar. Se consumen primero los documentos del plan, así que es el menor entre `recharged_documents` y lo que queda por consumir. | | `end_date` | `string` | Vencimiento de la suscripción y de sus recargas (`YYYY-MM-DD`). | | `remaining_days` | `integer` | Días calendario hasta el vencimiento; el día en curso cuenta. | | `expiry_warning` | `boolean` | `true` solo si quedan menos de 30 días **y** hay documentos recargados sin usar. | | `expiry_message` | `string` \| `null` | Texto del aviso cuando `expiry_warning` es `true`; `null` en otro caso. | | `transferred_count` | `integer` | Cantidad de recargas `TRANSFERRED` incluidas en `recharges`. | | `recharges` | `array` | Historial de recargas (ver abajo). | **Elementos de `recharges`:** | Campo | Tipo | Descripción | |---|---|---| | `id` | `integer` | Identificador de la recarga. | | `date` | `string` | Fecha y hora de la recarga (`YYYY-MM-DD HH:mm:ss`). | | `documents` | `integer` | Documentos recargados. En una `TRANSFERRED` es la cantidad que se recargó originalmente, no la que se trasladó: los documentos ya usados no pasan al plan nuevo. | | `status` | `string` | `ACTIVE`: del ciclo vigente, suma al límite y vence con la suscripción. `TRANSFERRED`: de un plan anterior de la misma empresa; sus documentos sin usar pasaron al plan actual en un cambio de plan. | | `transferred_at` | `string` \| `null` | Solo `TRANSFERRED`: fecha del traslado (`YYYY-MM-DD`). | | `from_plan_name` | `string` \| `null` | Solo `TRANSFERRED`: nombre del plan de origen. | :::tip Cómo leer las recargas - Las recargas no tienen vencimiento propio: vencen con la suscripción (`end_date`). - El orden es: primero las `ACTIVE` y después las `TRANSFERRED` (últimos 12 meses, máximo 50); en cada grupo, la más reciente primero. - Las `TRANSFERRED` no suman en `recharged_documents` ni en `unused_recharged_documents`: sus documentos sin usar ya están dentro de `base_documents`. - Para saber cuánto se recargó en el ciclo vigente, usa `recharged_documents` o suma solo los elementos `ACTIVE`. No sumes todos los `recharges[].documents`. - Sin recargas, `recharges` llega vacío y los totales de recarga en `0`. ::: **✅ Respuesta Exitosa (HTTP 200)** ```json { "plan_documents": 8000, "base_documents": 8200, "recharged_documents": 300, "total_documents": 8500, "unused_recharged_documents": 300, "end_date": "2026-10-29", "remaining_days": 9, "expiry_warning": true, "expiry_message": "Tu plan vence en 9 días (2026-10-29). Los documentos recargados que no uses antes de esa fecha se pierden.", "transferred_count": 1, "recharges": [ { "id": 12, "date": "2026-10-17 09:42:10", "documents": 300, "status": "ACTIVE", "transferred_at": null, "from_plan_name": null }, { "id": 7, "date": "2025-06-03 15:10:44", "documents": 500, "status": "TRANSFERRED", "transferred_at": "2025-10-29", "from_plan_name": "PLAN BÁSICO" } ], "success": true } ``` En el ejemplo, de los 500 documentos de la recarga `TRANSFERRED` quedaron 200 sin usar al cambiar de plan. Esos 200 pasaron al plan actual y están dentro de `base_documents` (8000 del plan + 200). La recarga sigue mostrando `documents: 500`. **❌ Sin suscripción activa (HTTP 404)** ```json { "success": false, "message": "No tienes una suscripción activa" } ``` **❌ Otros errores** | HTTP | Causa | |---|---| | `401` | Petición sin autenticar. | | `403` | Usuario sin empresa, empresa inactiva o `client_uuid` de una empresa que no es sub-cuenta activa de quien llama. | | `404` | `Empresa no encontrada.` | ### 🆕 Guía rápida: cuotas para clientes Usa siempre el token de la Casa de Software (usuario Administrador o Desarrollador). 1. **Obtén el `client_uuid` del cliente:** es el `uuid` de cada cliente en [`GET /company/customers`](https://docs.matias-api.com/docs/endpoints/company#clientes-casa-software), el mismo que usa el resto de la API. 2. **Verifica la disponibilidad (opcional):** `GET /memberships/summary`, **sin** `client_uuid` (así ves la bolsa de la Casa de Software). 3. **Asigna la cuota:** `POST /memberships/quotas` con `client_uuid` en el body (o en la query) y los campos en `snake_case`. 4. **Para ajustarla**, vuelve a llamar al `POST` con el nuevo total: reemplaza la cuota anterior, no se suma. 5. **Para liberarla**, `DELETE /memberships/quotas/{client_uuid}`. :::caution 🆕 Cambio del 2026-10-21: `client_uuid` reemplaza a `client_company_id` Antes el cliente se identificaba con su `id` numérico (`client_company_id`) y enviar `client_uuid` hacía que la API tomara al cliente como quien actúa, con un error. Ahora: - `client_uuid` es el identificador del cliente en el `POST` y en la ruta del `DELETE`. - La Casa de Software es siempre la empresa del token: un `client_uuid` ya no la reemplaza. - `client_company_id` en el `POST` (solo si no envías `client_uuid`) y el `id` numérico en la ruta del `DELETE` siguen funcionando, con la misma validación de pertenencia, pero están **obsoletos** y se retirarán. Migra a `client_uuid`. - Pueden gestionar cuotas los usuarios **Administrador** y **Desarrollador** (antes, asignar era solo para Desarrollador). ::: **POST /memberships/quotas — Asignar o Actualizar Cuota de un Cliente** ```http POST {{url}}/memberships/quotas Authorization: Bearer {token} Content-Type: application/json ``` Permite a una Casa de Software reservar una parte de la bolsa de documentos de su plan para una empresa cliente. Un cliente con cuota consume de su cuota; un cliente sin cuota consume de la bolsa compartida de la Casa de Software. Usa el token de la **Casa de Software** (usuario Administrador o Desarrollador). El cliente se identifica con `client_uuid`, en el body o en la query (`?client_uuid=...`); la Casa de Software que asigna es siempre la del token. **Parámetros del Body** (JSON plano, en `snake_case`): :::note Nombres de campos Los campos van en **snake_case**. Nombres en otro formato (`clientUuid`, `startDate`, `endDate`, `folios`) no se reconocen: se ignoran y la API responde `422` por los campos requeridos que faltan. ::: | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `client_uuid` | `string` (uuid) | ✅ Sí | 🆕 `uuid` del cliente (sub-cuenta) de la Casa de Software, tal como aparece en `GET /company/customers`. También se acepta en la query. | | `client_company_id` | `integer` | ⚠️ Obsoleto | `id` numérico del cliente. Solo se usa si no envías `client_uuid`. Se retirará: usa `client_uuid`. | | `assigned_documents` | `integer` | ✅ Sí | Documentos a reservar. Mínimo `0`. | | `start_date` | `string` | ✅ Sí | Inicio de vigencia de la cuota (`YYYY-MM-DD`). | | `end_date` | `string` | ✅ Sí | Fin de vigencia (`YYYY-MM-DD`). Debe ser igual o posterior a `start_date`. | ```json { "client_uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "assigned_documents": 500, "start_date": "2026-10-14", "end_date": "2027-10-13" } ``` **Reglas de negocio:** - Es un **upsert**: si el cliente ya tiene cuota en la suscripción activa, la cuota se **reemplaza** con los nuevos valores; no se suma a la anterior. - El `client_uuid` debe ser de un cliente de la Casa de Software del token. Un cliente de otra cuenta, o un uuid que no existe, responde `404 El cliente no existe.` También se acepta un cliente **deshabilitado**: su `uuid` aparece en `GET /company/customers?is_active=0`, porque el listado por defecto solo trae los activos. - La Casa de Software debe tener una suscripción activa que no sea el plan FREE. - En planes con bolsa finita, `assigned_documents` no puede superar lo disponible: el menor entre (bolsa − asignado a otros clientes con fechas que se solapan) y (bolsa − consumido). - En planes ilimitados no se valida la bolsa: la cuota es opcional y funciona como límite de control para ese cliente. **✅ Respuesta Exitosa (HTTP 201)** ```json { "message": "Cuota asignada correctamente.", "quota": { "id": 58, "uuid": "9b2f6d0e-3c1a-4f7e-8a51-2d6c0b9e4f13", "subscription_id": 311, "client_company_id": 1262, "assigned_documents": 500, "start_date": "2026-10-14T05:00:00.000000Z", "end_date": "2027-10-13T05:00:00.000000Z", "created_at": "2026-10-14T20:15:03.000000Z", "updated_at": "2026-10-14T20:15:03.000000Z", "client_uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220" }, "success": true } ``` 🆕 `quota.client_uuid` devuelve el `uuid` del cliente. `quota.client_company_id` es su `id` interno. **❌ Documentos insuficientes (HTTP 409)** ```json { "message": "No tiene suficientes documentos disponibles para asignar. Disponibles: 2800, Solicitados: 3000", "available_documents": 2800, "requested_documents": 3000, "total_documents": 10000, "consumed_documents": 7200, "already_assigned": 0, "success": false } ``` La validación final de disponibilidad, que descuenta lo asignado a otros clientes, puede responder también con este formato: ```json { "success": false, "message": "No tienes suficientes documentos disponibles para asignar. Disponibles: 2800 (Bolsa: 10000, Asignados: 2500, Consumidos: 7200)" } ``` **❌ Error de validación (HTTP 422)** ```json { "message": "La fecha de finalización es obligatoria.", "errors": { "end_date": ["La fecha de finalización es obligatoria."] } } ``` Con el body en `camelCase` (por ejemplo `clientUuid`, `startDate`, `endDate`) faltan los cuatro campos y `errors` los lista todos. El `message` es el primer error seguido del total restante, que la API devuelve en inglés: ```json { "message": "El UUID del cliente (client_uuid) es obligatorio. (and 3 more errors)", "errors": { "client_uuid": ["El UUID del cliente (client_uuid) es obligatorio."], "assigned_documents": ["El número de documentos asignados es obligatorio."], "start_date": ["La fecha de inicio es obligatoria."], "end_date": ["La fecha de finalización es obligatoria."] } } ``` **❌ Otros errores** | HTTP | Mensaje | Causa | |---|---|---| | `400` | `No tienes permisos para asignar cuotas a clientes.` | El usuario del token no es Administrador ni Desarrollador. | | `402` | `El plan FREE no permite asignar cuotas a clientes. Actualice su plan para acceder a esta funcionalidad.` | La Casa de Software tiene plan FREE. | | `402` | `El desarrollador no tiene una suscripción activa.` | No hay suscripción vigente. | | `403` | `Usuario no asociado a ninguna empresa.` / `Acceso no autorizado. La empresa se encuentra inactiva.` | El usuario del token no tiene empresa, o la empresa del token está inactiva. | | `404` | `El cliente no existe.` | 🆕 El `client_uuid` (o el `client_company_id` obsoleto) no es de un cliente de la Casa de Software del token, o no existe. | | `422` | `El UUID del cliente (client_uuid) es obligatorio.` | Falta `client_uuid` (y tampoco se envió `client_company_id`), o el body no está en `snake_case`. | | `422` | `La fecha de inicio es obligatoria.` / `La fecha de finalización debe ser igual o posterior a la fecha de inicio.` | Fechas ausentes o fuera de orden. | Los controles se aplican en este orden: datos del body (`422`), tipo de usuario (`400`), empresa del token (`403`), cliente propio (`404`), plan FREE (`402`), disponibilidad (`409`) y suscripción activa (`402`). **DELETE /memberships/quotas/{client_uuid} — Eliminar Cuota de un Cliente** ```http DELETE {{url}}/memberships/quotas/{client_uuid} Authorization: Bearer {token} ``` Elimina la cuota del cliente. Desde ese momento el cliente vuelve a consumir de la bolsa compartida de la Casa de Software, o de forma ilimitada si el plan es ilimitado. Usa el token de la Casa de Software (usuario Administrador o Desarrollador). **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | path | ✅ Sí | 🆕 `uuid` del cliente de la Casa de Software. Un valor numérico se toma como el `id` interno del cliente: es la forma **obsoleta**, que se retirará. | | `consumption_mode` | Significado | |---|---| | `shared_pool` | El cliente consume de la bolsa compartida. | | `unlimited` | El plan de la Casa de Software es ilimitado. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Cuota eliminada correctamente. El cliente ahora consume de la bolsa general.", "data": { "client_uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "client_company_id": 1262, "consumption_mode": "shared_pool" }, "success": true } ``` Es **idempotente**: si el cliente no tenía cuota, la respuesta también es `200`, con el mensaje `El cliente no tenía cuota asignada. Ya consume de la bolsa general.` y el mismo `data`. **❌ Errores** | HTTP | Mensaje | Causa | |---|---|---| | `402` | `No tienes permisos para eliminar cuotas de clientes.` | El usuario del token no es Administrador ni Desarrollador. | | `402` | `El desarrollador no tiene una suscripción activa.` | No hay suscripción vigente. | | `403` | `Usuario no asociado a ninguna empresa.` / `Acceso no autorizado. La empresa se encuentra inactiva.` | El usuario del token no tiene empresa, o la empresa del token está inactiva. | | `404` | `El cliente no existe.` | 🆕 El `client_uuid` no es de un cliente de la Casa de Software del token, o no existe. Un cliente deshabilitado sí se acepta. Antes, un cliente ajeno respondía `422`. | --- ## 💾 Almacenamiento y Correos del Plan Consulta el uso de almacenamiento, de adjuntos y de correos frente a los límites del plan. Aceptan `client_uuid` para consultar una sub-cuenta propia; una sub-cuenta se mide contra el plan de su Casa de Software, pero solo cuenta su propio uso. En `storage` y `emails`, una Casa de Software (cuenta principal) ve el uso sumado de su cuenta y de sus sub-cuentas; `attachments` cuenta solo los adjuntos de la empresa consultada. **GET /memberships/server-stats — Estadísticas Completas** ```http GET {{url}}/memberships/server-stats?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve en una sola llamada los objetos `storage`, `attachments` y `emails`. **Objeto `storage`:** | Campo | Tipo | Descripción | |---|---|---| | `used_gb` | `number` | Almacenamiento usado (GB). | | `limit_gb` | `number` | Límite del plan (GB). `0` significa sin límite. | | `usage_percent` | `number` | Porcentaje de uso. | | `files_count` | `integer` | Cantidad de archivos. | | `plan_name` | `string` | Nombre del plan. | | `is_unlimited` | `boolean` | `true` si el plan no limita el almacenamiento. | | `status` | `string` | `unlimited`, `normal`, `warning` (desde 70 %), `critical` (desde 90 %) o `exceeded` (desde 100 %). Sin suscripción: `no_subscription`. | **Objeto `attachments`** (adjuntos de los documentos): | Campo | Tipo | Descripción | |---|---|---| | `used_gb` | `number` | Espacio usado por adjuntos (GB). | | `used_bytes` | `integer` | Espacio usado por adjuntos (bytes). | | `files_count` | `integer` | Cantidad de adjuntos. | | `limit_gb` | `number` \| `null` | Límite de adjuntos (GB). `null` en el plan de Arrendamiento, donde los adjuntos comparten la cuota de `storage`. | | `usage_percent` | `number` | Porcentaje de uso. | | `is_shared` | `boolean` | `true` si los adjuntos comparten cuota con `storage`. | | `plan_name` | `string` | Nombre del plan (`SIN SUSCRIPCIÓN` si no hay). | | `status` | `string` | `shared_with_custody`, `unlimited`, `normal`, `warning`, `critical`, `exceeded` o `no_subscription`. | **Objeto `emails`** (correos del periodo de la suscripción activa): | Campo | Tipo | Descripción | |---|---|---| | `used` | `integer` | Correos enviados en el periodo. | | `limit` | `integer` | Correos incluidos en el plan. `0` significa sin límite. | | `remaining` | `integer` | Correos restantes. `-1` significa ilimitado. | | `usage_percent` | `number` | Porcentaje de uso. | | `plan_name` | `string` | Nombre del plan. | | `status` | `string` | `unlimited`, `normal`, `critical` (desde 90 %) o `exceeded` (desde 100 %). Sin suscripción: `no_subscription`. | Si un objeto no se puede calcular, llega como `{"error": "mensaje"}` sin afectar a los demás. **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Estadísticas completas obtenidas con éxito", "data": { "storage": { "used_gb": 3.42, "limit_gb": 50, "usage_percent": 6.84, "files_count": 18250, "plan_name": "PLAN CASA DE SOFTWARE", "is_unlimited": false, "status": "normal" }, "attachments": { "used_gb": 0.2154, "used_bytes": 231289856, "files_count": 412, "limit_gb": 50, "usage_percent": 0.43, "is_shared": false, "plan_name": "PLAN CASA DE SOFTWARE", "status": "normal" }, "emails": { "used": 8420, "limit": 20000, "remaining": 11580, "usage_percent": 42.1, "plan_name": "PLAN CASA DE SOFTWARE", "status": "normal" } }, "success": true } ``` **GET /memberships/server-stats/storage — Almacenamiento** ```http GET {{url}}/memberships/server-stats/storage?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve los campos del objeto `storage` en el primer nivel de `data`, más el objeto `attachments`. **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Estadísticas de almacenamiento obtenidas con éxito", "data": { "used_gb": 3.42, "limit_gb": 50, "usage_percent": 6.84, "files_count": 18250, "plan_name": "PLAN CASA DE SOFTWARE", "is_unlimited": false, "status": "normal", "attachments": { "used_gb": 0.2154, "used_bytes": 231289856, "files_count": 412, "limit_gb": 50, "usage_percent": 0.43, "is_shared": false, "plan_name": "PLAN CASA DE SOFTWARE", "status": "normal" } }, "success": true } ``` **GET /memberships/server-stats/emails — Correos** ```http GET {{url}}/memberships/server-stats/emails?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve los campos del objeto `emails` en `data`. **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Estadísticas de emails obtenidas con éxito", "data": { "used": 8420, "limit": 20000, "remaining": 11580, "usage_percent": 42.1, "plan_name": "PLAN CASA DE SOFTWARE", "status": "normal" }, "success": true } ``` **GET /memberships/server-stats/emails/resend-count/{documentId} — Reenvíos de un Documento** ```http GET {{url}}/memberships/server-stats/emails/resend-count/{documentId}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Indica cuántas veces se ha enviado por correo un documento y cuántos reenvíos le quedan según el plan. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `documentId` | path | ✅ Sí | ID del documento. | | `client_uuid` | query | No | UUID de una sub-cuenta propia y activa. | | Campo | Tipo | Descripción | |---|---|---| | `current_count` | `integer` | Envíos registrados del documento. | | `max_allowed` | `integer` | Reenvíos permitidos por el plan. `0` significa ilimitado. | | `remaining` | `integer` | Reenvíos restantes. `-1` significa ilimitado. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Estadísticas de reenvío obtenidas con éxito", "data": { "current_count": 2, "max_allowed": 5, "remaining": 3 }, "success": true } ``` --- # 🔔 Webhooks :::info 🆕 Nuevo en v3.0.0 · ✅ Autenticación requerida Recibe notificaciones HTTP en tiempo real cuando ocurren eventos importantes (documentos creados, emitidos o validados por la DIAN, correos enviados y entregados, lotes masivos terminados). **18 eventos suscribibles.** ::: :::info ¿Dónde obtener el `client_uuid`? — Multi-Tenant para Casas de Software Si operas como **Casa de Software** o **Cuenta Principal**, puedes listar, registrar y administrar los webhooks de cada una de tus empresas cliente agregando el parámetro `?client_uuid={{client_uuid}}` en la query string de **cualquier** endpoint de esta sección: - **URL con Query Param:** `{{url}}/webhooks?client_uuid={{client_uuid}}` - **Header:** `Authorization: Bearer {token_cuenta_principal}` - **Comportamiento:** La configuración del webhook quedará vinculada específicamente a la empresa cliente indicada por su UUID. **¿Dónde encontrar el `client_uuid` de tus clientes?** ```http GET {{url}}/company/customers Authorization: Bearer {token} Content-Type: application/json ``` ::: :::note URL base En esta página, `{{url}}` es la URL base del API **incluyendo** `/api/ubl2.1` (por ejemplo, en sandbox: `https://sandbox-api.matias-api.com/api/ubl2.1`). Las rutas de webhooks cuelgan directamente de ese prefijo: `{{url}}/webhooks`. No repitas `/ubl2.1` en la ruta; `{{url}}/ubl2.1/webhooks` responde 404. ::: **Características principales:** - 🔔 **18 tipos de eventos:** Documentos, correos, lotes masivos (y eventos reservados de pagos y cuotas) - 🔐 **Firma HMAC-SHA256:** Verificación de autenticidad sobre el cuerpo exacto recibido - 🔄 **Reintentos automáticos:** Hasta 6 reintentos con espera creciente - 🎨 **Headers personalizados:** Agrega tus propios headers - 🧪 **Testing integrado:** Prueba webhooks antes de producción - 📊 **Historial completo:** Rastreo de entregas exitosas y fallidas, con reintento manual --- ## 📋 Eventos Disponibles **GET /webhooks/events — Listar Todos los Eventos** ```http GET {{url}}/webhooks/events Authorization: Bearer {token} ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "documents": [ { "value": "document.created", "label": "Documento creado en el sistema" }, { "value": "document.emitted", "label": "Documento enviado a DIAN" } ], "emails": [ { "value": "email.sent", "label": "Email enviado al destinatario" } ], "payments": [ { "value": "payment.approved", "label": "Pago procesado exitosamente" } ], "memberships": [ { "value": "quota.limit_reached", "label": "80% de cuota mensual alcanzada" } ] } }, "success": true } ``` El ejemplo está recortado: cada categoría trae todos sus eventos. El listado **no incluye** la categoría de lotes masivos, pero `bulk.batch.completed` sí se puede suscribir. ### Catálogo de Eventos | Categoría | Evento | Descripción | |-----------|--------|-------------| | **📄 Documentos** | `document.created` | Documento creado en el sistema | | | `document.emitted` | Documento enviado a DIAN | | | `document.accepted` | Documento aceptado por DIAN | | | `document.rejected` | Documento rechazado por DIAN | | | `document.voided` | Documento anulado | | **📧 Emails** | `email.sent` | Email enviado al destinatario | | | `email.delivered` | Email entregado al destinatario | | | `email.bounced` | Email rebotado | | | `email.complaint` | Email marcado como spam | | | `email.opened` | Email abierto por el destinatario | | | `email.clicked` | Link clickeado en el email | | **📦 Lotes masivos** | `bulk.batch.completed` | Lote de documentos masivos completado | | **💳 Pagos** *(reservado)* | `payment.approved` | Pago procesado exitosamente | | | `payment.declined` | Pago rechazado | | | `payment.pending` | Pago en proceso | | **🏷️ Membresías y cuotas** *(reservado)* | `quota.limit_reached` | 80% de cuota mensual alcanzada | | | `quota.limit_exceeded` | Cuota mensual excedida | | | `token.expiring_soon` | Token expira en 7 días | :::caution Eventos reservados Los eventos `payment.*`, `quota.*` y `token.*` se aceptan al suscribirse, pero **aún no se emiten**: hoy solo se disparan los eventos de documentos, de correos y `bulk.batch.completed`. Cualquier evento fuera de este catálogo (por ejemplo `payment.error` o `membership.activated`, que no existen) se rechaza al crear o actualizar el webhook con HTTP 422: ```json { "success": false, "message": "Evento inválido: membership.activated" } ``` ::: --- ## 🔧 Gestión de Webhooks **GET /webhooks — Listar Webhooks** ```http GET {{url}}/webhooks?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "id": 1, "name": "Webhook Producción", "url": "https://tu-servidor.com/webhook/matias", "events": ["document.created", "document.emitted"], "is_active": true, "last_triggered_at": "2026-02-06T16:45:00-05:00", "created_at": "2026-02-01T10:00:00-05:00", "stats": { "total": 150, "delivered": 144, "failed": 3, "pending": 2, "retrying": 1 }, "recent_deliveries": [ { "id": 2746, "event_type": "document.created", "status": "delivered", "delivered_at": "2026-02-06T16:45:00-05:00" } ] } ], "total": 1 }, "success": true } ``` - `stats.total` es la suma de los cuatro estados (`delivered` + `failed` + `pending` + `retrying`). - `recent_deliveries` trae como máximo las **5** entregas más recientes, la última primero. - `last_triggered_at` es `null` si el webhook nunca ha tenido una entrega exitosa. - El listado **no** devuelve el `secret` ni los `headers`; consúltalos en el detalle. **GET /webhooks/{webhook_id} — Detalle de un Webhook** ```http GET {{url}}/webhooks/{webhook_id}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "id": 1, "name": "Webhook Producción", "url": "https://tu-servidor.com/webhook/matias", "events": ["document.created", "document.emitted"], "is_active": true, "last_triggered_at": "2026-02-06T16:45:00-05:00", "created_at": "2026-02-01T10:00:00-05:00", "secret": "whsec_EJEMPLOxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "headers": { "X-Custom-Header": "valor-personalizado" }, "stats": { "total": 150, "delivered": 144, "failed": 3, "pending": 2, "retrying": 1 } } }, "success": true } ``` El detalle devuelve el `secret` vigente, así que puedes consultarlo en cualquier momento. Para invalidarlo y cambiarlo, usa `POST /webhooks/{webhook_id}/regenerate-secret`. **❌ Webhook no encontrado (HTTP 404)** Un webhook inexistente o de otra empresa responde igual en todos los endpoints de esta sección: ```json { "success": false, "message": "Webhook no encontrado." } ``` **POST /webhooks — Crear Webhook** ```http POST {{url}}/webhooks?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros del Body:** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `name` | `string` | ✅ Sí | Nombre descriptivo del webhook (máx. 100 caracteres) | | `url` | `string` | ✅ Sí | URL donde recibirás las notificaciones (máx. 500 caracteres). En producción debe ser HTTPS y pública | | `events` | `array` | ✅ Sí | Eventos a los que te suscribes (mínimo 1), cada uno un `string` del [catálogo](https://docs.matias-api.com/docs/endpoints/webhooks#eventos-disponibles) | | `headers` | `object` | No | Headers personalizados (clave → valor `string`) que se incluyen en cada entrega | | `is_active` | `boolean` | No | Si el webhook está activo (default: `true`) | **Reglas de validación** (HTTP 422): | Mensaje | Causa | |---|---| | `URL inválida` | La URL no tiene un formato válido. | | `Solo se permiten URLs HTTPS en producción` | *(Solo en producción)* La URL no empieza por `https://`. | | `No se permiten URLs locales o privadas en producción` | *(Solo en producción)* `localhost`, `127.0.0.1`, `::1`, `0.0.0.0` o IP de rangos privados o reservados. | | `Dominio bloqueado: webhook.site` | *(Solo en producción)* Dominios de captura públicos (`webhook.site`, `requestbin.com`, `hookbin.com`). | | `Debe seleccionar al menos un evento` | `events` vacío. | | `Evento inválido: ` | El evento no está en el catálogo. | | `No se puede sobrescribir el header del sistema:
` | Un header personalizado usa un nombre reservado: `Content-Type`, `X-Webhook-Signature`, `X-Webhook-Id`, `X-Webhook-Event`, `X-Webhook-Timestamp` o `User-Agent`. | | `El valor del header debe ser una cadena` | Un valor de `headers` no es `string`. | Estos errores responden `{"success": false, "message": "..."}`. Los errores de formato del body (campo faltante, tipo incorrecto, longitud) responden en el formato estándar de validación: HTTP 422 con `message` y `errors` por campo. **cURL** ```bash curl -X POST "{{url}}/webhooks" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "name": "Webhook Producción", "url": "https://tu-servidor.com/webhook/matias", "events": ["document.created", "document.emitted", "email.sent"], "headers": { "X-Custom-Header": "valor-personalizado" }, "is_active": true }' ``` **JavaScript (Axios)** ```js const response = await axios.post(`${url}/webhooks`, { name: 'Webhook Producción', url: 'https://tu-servidor.com/webhook/matias', events: ['document.created', 'document.emitted', 'email.sent'], headers: { 'X-Custom-Header': 'valor-personalizado' }, is_active: true }, { headers: { Authorization: `Bearer ${token}` } }); // Guarda el secret para verificar la firma HMAC const secret = response.data.dataRecords.data.secret; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/webhooks", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'name' => 'Webhook Producción', 'url' => 'https://tu-servidor.com/webhook/matias', 'events' => ['document.created', 'document.emitted'], 'is_active' => true, ], ]); $secret = json_decode($response->getBody())->dataRecords->data->secret; ``` **✅ Respuesta Exitosa (HTTP 201)** ```json { "dataRecords": { "data": { "id": 1, "name": "Webhook Producción", "url": "https://tu-servidor.com/webhook/matias", "secret": "whsec_EJEMPLOxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "events": ["document.created", "document.emitted"], "is_active": true, "created_at": "2026-02-01T10:00:00-05:00" } }, "message": "Webhook creado exitosamente", "success": true } ``` :::tip Guarda el `secret` El servidor genera el `secret` con el formato `whsec_` seguido de 40 caracteres. Lo necesitas para verificar la firma HMAC. Si lo pierdes, puedes consultarlo de nuevo en `GET /webhooks/{webhook_id}`. ::: **PUT /webhooks/{webhook_id} — Actualizar Webhook** ```http PUT {{url}}/webhooks/{webhook_id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook a actualizar. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **Body:** mismos campos que en `POST`, todos opcionales. La actualización es parcial para `name`, `url`, `events` e `is_active`: solo cambia lo que envías. Se aplican las mismas reglas de validación. :::warning Los `headers` no son parciales Si el `PUT` no incluye `headers`, los headers personalizados del webhook **se borran**. Para conservarlos, reenvíalos siempre en cada actualización. ::: ```json { "name": "Webhook Actualizado", "url": "https://nuevo-servidor.com/webhook", "events": ["document.created", "document.accepted"], "headers": { "X-Custom-Header": "valor-personalizado" }, "is_active": true } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "id": 21, "name": "Webhook Actualizado", "url": "https://nuevo-servidor.com/webhook", "events": ["document.created", "document.accepted"], "is_active": true } }, "message": "Webhook actualizado exitosamente", "success": true } ``` **DELETE /webhooks/{webhook_id} — Eliminar Webhook** ```http DELETE {{url}}/webhooks/{webhook_id}?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook a eliminar. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true, "message": "Webhook eliminado exitosamente" } ``` **POST /webhooks/{webhook_id}/test — Probar Webhook** ```http POST {{url}}/webhooks/{webhook_id}/test?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Envía de inmediato un evento de prueba (`type: "webhook.test"`) a la URL del webhook y espera la respuesta de tu servidor. Funciona aunque el webhook esté inactivo. La entrega queda registrada en el historial y, si falla, entra en la política de [reintentos automáticos](https://docs.matias-api.com/docs/endpoints/webhooks#reintentos). El evento de prueba llega con este `data`: ```json { "message": "Este es un webhook de prueba desde APIDIAN", "webhook_id": 21, "timestamp": "2026-02-06T16:45:00-05:00" } ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook a probar. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** Tu servidor respondió con un código 2xx: ```json { "success": true, "message": "Webhook de prueba enviado exitosamente", "dataRecords": { "data": { "delivery_id": 2747, "status_code": 200, "response": "{\"received\":true}", "delivered_at": "2026-02-06T16:45:00-05:00" } } } ``` **❌ Entrega fallida (HTTP 424)** Tu servidor respondió con un código distinto de 2xx, o hubo un error de conexión o timeout. En el caso de error de conexión, `status_code` es `null` y `response` trae el mensaje del error: ```json { "success": false, "message": "Webhook de prueba falló. Se reintentará automáticamente.", "dataRecords": { "data": { "delivery_id": 2748, "status_code": 500, "response": "Internal Server Error", "delivered_at": "2026-02-06T16:46:00-05:00" } } } ``` **GET /webhooks/{webhook_id}/deliveries — Historial de Entregas** ```http GET {{url}}/webhooks/{webhook_id}/deliveries?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Retorna el historial paginado (15 por página) de entregas del webhook, la más reciente primero. Usa `?page=` para navegar. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook. | | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 2746, "webhook_id": 21, "event_type": "document.created", "payload": { "id": "evt_EJEMPLOxxxxxxxxxxxxxxxxx", "type": "document.created", "created_at": "2026-02-06T16:45:00-05:00", "api_version": "3.0", "data": { "object": "shipping_history", "id": 12345 } }, "signature": "5f2b9c...e81a", "status": "delivered", "http_status": 200, "response_body": "{\"received\":true}", "retry_count": 0, "delivered_at": "2026-02-06T16:45:00.000000Z", "next_retry_at": null } ], "first_page_url": "{{url}}/webhooks/21/deliveries?page=1", "last_page": 1, "per_page": 15, "total": 1 }, "success": true } ``` | Campo | Descripción | |---|---| | `payload` | Sobre completo que se envió (ver [Payload recibido](https://docs.matias-api.com/docs/endpoints/webhooks#payload-recibido)). | | `signature` | Firma HMAC enviada en el último intento. | | `status` | `pending` (esperando reintento), `retrying` (reintento en curso), `delivered` o `failed` (reintentos agotados). | | `http_status` | Código HTTP que respondió tu servidor (`null` si no hubo respuesta). | | `response_body` | Cuerpo de la respuesta de tu servidor, o el mensaje del error de conexión. | | `retry_count` | Reintentos realizados (0 a 6). | | `next_retry_at` | Fecha del próximo reintento automático, si lo hay. | El paginador incluye además los campos estándar (`from`, `to`, `links`, `next_page_url`, `prev_page_url`, `path`, `last_page_url`). **POST /webhooks/{webhook_id}/deliveries/{delivery_id}/retry — 🆕 Reintentar una Entrega** ```http POST {{url}}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Reenvía al instante el payload original de una entrega, sin esperar al próximo reintento programado. La firma y los headers se recalculan con el `secret` vigente, y se añade `X-Webhook-Retry-Count`. Si la entrega ya había agotado los 6 reintentos, el contador vuelve a 0 antes de reenviar. No lleva body. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook de la empresa. | | `delivery_id` | path | ✅ Sí | ID de una entrega de ese webhook (del historial de entregas). | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true, "message": "Entrega reintentada y confirmada exitosamente", "dataRecords": { "data": { "delivery_id": 2746, "status_code": 200, "response": "{\"received\":true}", "retry_count": 1, "delivered_at": "2026-02-06T17:10:00-05:00" } } } ``` **❌ Reintento fallido (HTTP 424)** Tu servidor volvió a responder con un código distinto de 2xx, hubo un error de conexión, o el webhook está **inactivo** (en ese caso no se envía nada): ```json { "success": false, "message": "El reintento falló nuevamente. Revise la respuesta del endpoint receptor.", "dataRecords": { "data": { "delivery_id": 2746, "status_code": 500, "response": "Internal Server Error", "retry_count": 2, "delivered_at": "2026-02-06T17:10:00-05:00" } } } ``` **❌ No encontrado (HTTP 404)** ```json { "success": false, "message": "Entrega de webhook no encontrada." } ``` Si el que no existe es el webhook, el mensaje es `Webhook no encontrado.` **POST /webhooks/{webhook_id}/regenerate-secret — Regenerar Secret HMAC** ```http POST {{url}}/webhooks/{webhook_id}/regenerate-secret?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Genera un nuevo secret (`whsec_` seguido de 40 caracteres) para verificar la firma HMAC. El secret anterior queda **inmediatamente inválido**. :::warning Acción crítica Actualiza tu sistema inmediatamente con el nuevo secret. Las entregas que se envíen o reintenten desde ese momento se firman con el secret nuevo. ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `webhook_id` | path | ✅ Sí | ID del webhook. | | `client_uuid` | query | No | UUID del cliente (Multi-Tenant). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "secret": "whsec_NUEVOxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } }, "message": "Secret regenerado exitosamente", "success": true } ``` --- ## 🔐 Seguridad HMAC ### Payload Recibido Cuando ocurre un evento, recibirás un `POST` en tu URL con este sobre: ```json { "id": "evt_EJEMPLOxxxxxxxxxxxxxxxxx", "type": "document.created", "created_at": "2026-02-06T16:45:00-05:00", "api_version": "3.0", "data": { "object": "shipping_history", "id": 12345, "uuid": "8f14e45f-ceea-467a-9b2d-0a1b2c3d4e5f", "type_document": { "id": 7, "code": "01", "name": "Factura electrónica de venta" }, "operation_type": { "id": 1, "code": "10", "name": "Estándar" }, "document_number": "SETP990000001", "order_number": "PED-1001", "resolution": { "prefix": "SETP", "resolution_number": "18760000001" }, "company": { "name": "EMPRESA DE EJEMPLO SAS", "dni": "900000000", "email": "facturacion@ejemplo.com" }, "customer": { "name": "CLIENTE DE EJEMPLO SAS", "dni": "800000000", "email": "cliente@ejemplo.com" }, "legal_monetary_totals": { "payable_amount": "1190000.00" }, "invoice_date": "2026-02-06T00:00:00.000000Z", "status": 1, "is_valid": 1, "cufe": "cufe-de-ejemplo", "xml_file_name": "fv09000000000002600000001.xml", "qr_url": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=cufe-de-ejemplo", "paths": { "pdf": "...", "xml": "...", "zip": "...", "qr": "..." }, "created_at": "06-02-2026 04:45:00 pm", "updated_at": "06-02-2026 04:45:05 pm" } } ``` | Campo | Descripción | |---|---| | `id` | Identificador único del evento: `evt_` seguido de 24 caracteres. Es el mismo valor del header `X-Webhook-Id` y **se conserva en los reintentos**. | | `type` | Tipo de evento (ver [catálogo](https://docs.matias-api.com/docs/endpoints/webhooks#eventos-disponibles)). | | `created_at` | Fecha del evento en ISO-8601. | | `api_version` | Versión del formato del sobre (`3.0`). | | `data` | Datos del evento; su forma depende del prefijo de `type`. | **Forma de `data` según el evento:** **`document.*` — Documentos electrónicos** Campos: `object` (`shipping_history`), `id`, `uuid`, `type_document` (`id`, `code`, `name`), `operation_type` (`id`, `code`, `name`), `document_number`, `order_number`, `resolution` (`prefix`, `resolution_number`), `company` (`name`, `dni`, `email`), `customer` (`name`, `dni`, `email`; `name` es `Unknown` si falta), `legal_monetary_totals`, `invoice_date`, `status`, `is_valid`, `cufe`, `xml_file_name`, `qr_url`, `paths` (`pdf`, `xml`, `zip`, `qr`), `created_at`, `updated_at`. - `qr_url` es la URL de consulta en la DIAN del CUFE/CUDE vigente (`cufe`), nunca la de otro intento con el mismo número. Es `null` si el documento no tiene datos JSON. **🆕 Cuándo se emite `document.accepted`:** - Cuando la emisión queda validada por la DIAN. - Cuando la API devuelve un documento que la DIAN ya había autorizado (advertencia `ALREADY_AUTHORIZED_RECOVERED`; ver [Regla 90](https://docs.matias-api.com/docs/endpoints/invoices-pos#regla-90)). - Puede llegar más tarde para un documento antiguo que se concilia con la DIAN. **No se emite** cuando el documento queda validado después, con `POST /status/document/{trackId}` o con la revalidación automática. Si dependes de ese estado, consúltalo con esos endpoints. Si el documento ya no existe al construir el evento: ```json { "object": "shipping_history", "id": 12345, "error": "Document not found" } ``` **`email.*` — Correos** ```json { "object": "email_log", "id": 9876, "message_id": "0100018d-ejemplo-000000", "document": { "id": 12345, "type": "Factura electrónica de venta", "number": "SETP990000001", "uuid": "8f14e45f-ceea-467a-9b2d-0a1b2c3d4e5f" }, "recipient": "cliente@ejemplo.com", "status": "delivered", "delivered_at": "2026-02-06T16:46:00-05:00", "bounced_at": null, "opened_at": null, "clicked_at": null, "bounce_type": null, "opens_count": 0, "clicks_count": 0 } ``` Si el registro del correo no existe: `{"object": "email_log", "id": null, "error": "Email log not found"}`. **`bulk.batch.completed` — Lotes masivos** ```json { "object": "document_batch", "batch_id": "b5e2c1d0-0000-4000-8000-000000000000", "status": "completed", "total_items": 100, "succeeded": 98, "failed": 2, "finished_at": "2026-02-06T17:00:00-05:00" } ``` **`quota.*`, `token.*` y `payment.*` — Reservados** Estos eventos aún no se emiten. La forma de su `data` se documentará cuando se activen; mientras tanto, no dependas de ellos en tu integración. **Headers que recibes:** ``` Content-Type: application/json X-Webhook-Signature: 5f2b9c0d...e81a (HMAC-SHA256 en hexadecimal, sin prefijo) X-Webhook-Id: evt_EJEMPLOxxxxxxxxxxxxxxxxx X-Webhook-Event: document.created X-Webhook-Timestamp: 1770414300 (epoch Unix en segundos) User-Agent: APIDIAN-Webhooks/3.0 X-Webhook-Retry-Count: 1 (solo en reintentos, de 1 a 6) ``` - Tus headers personalizados se añaden a estos. - `X-Webhook-Timestamp` y la firma se recalculan en cada reintento; `X-Webhook-Id` se mantiene. - Los nombres de header no distinguen mayúsculas (en Node.js llegan en minúsculas: `x-webhook-signature`). ### Verificar Firma HMAC La firma es `HMAC-SHA256(cuerpo, secret)` en hexadecimal, donde `cuerpo` son **exactamente los bytes** del `POST` que recibes. Para validarla: 1. Lee el cuerpo **crudo** de la petición. No lo parsees y vuelvas a serializar: `JSON.stringify` o `json_encode` no reproducen los mismos bytes (escapes de `/` y de caracteres Unicode, espacios) y la firma no coincidirá. 2. Calcula el HMAC-SHA256 en hexadecimal con tu `secret`. 3. Compáralo con `X-Webhook-Signature` en **tiempo constante**. **Node.js** ```javascript const crypto = require('crypto'); const express = require('express'); const app = express(); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // whsec_... function verifyWebhook(rawBody, signature, secret) { if (typeof signature !== 'string') return false; const expected = crypto .createHmac('sha256', secret) .update(rawBody) // Buffer con el cuerpo crudo .digest('hex'); const a = Buffer.from(expected, 'utf8'); const b = Buffer.from(signature, 'utf8'); return a.length === b.length && crypto.timingSafeEqual(a, b); } // express.raw() entrega el cuerpo sin parsear (Buffer) app.post('/webhook/matias', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-webhook-signature']; if (!verifyWebhook(req.body, signature, WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body.toString('utf8')); // ✅ Responde rápido y procesa el evento en segundo plano (event.id, event.type, event.data) res.status(200).json({ received: true }); }); ``` **PHP** ```php $secret = getenv('WEBHOOK_SECRET'); // whsec_... $raw = file_get_contents('php://input'); // cuerpo crudo $signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? ''; $expected = hash_hmac('sha256', $raw, $secret); if (!hash_equals($expected, $signature)) { http_response_code(401); exit('Invalid signature'); } $event = json_decode($raw, true); // ✅ Procesar el evento ($event['id'], $event['type'], $event['data']) http_response_code(200); echo json_encode(['received' => true]); ``` :::warning Protección contra repetición La firma no incluye `X-Webhook-Timestamp`. La protección contra repeticiones corre por tu cuenta: guarda el `id` del evento (`X-Webhook-Id`) y descarta los que ya procesaste. ::: --- ## 🔄 Reintentos Automáticos :::tip Política de reintentos Una entrega falla si tu servidor responde con un código distinto de 2xx, o si hay un error de conexión o un timeout. El timeout es de **30 segundos** por intento. Las entregas fallidas se reintentan automáticamente: ::: | Reintento | Espera desde el intento anterior | |---------|--------| | 1 | 1 minuto | | 2 | 5 minutos | | 3 | 30 minutos | | 4 | 2 horas | | 5 | 4 horas | | 6 | Sin espera adicional (en la siguiente pasada, cerca de 1 minuto) | - Mientras espera, la entrega está en `pending` con `next_retry_at`; durante el intento pasa a `retrying`. - Hay como máximo **6 reintentos**. Si se agotan, la entrega queda en `failed`. - Los webhooks **inactivos** no se reintentan. - Cada reintento conserva el `id` del evento y envía `X-Webhook-Retry-Count`. - Puedes reintentar una entrega en cualquier momento con [`POST /webhooks/{webhook_id}/deliveries/{delivery_id}/retry`](https://docs.matias-api.com/docs/endpoints/webhooks#gestion-webhooks), incluso si ya agotó los reintentos. --- ## 💡 Mejores Prácticas | Práctica | Descripción | |----------|-------------| | ⚡ **Responde rápido** | Responde `2xx` en menos de 5 segundos (el límite real es de 30 segundos; pasado ese tiempo la entrega cuenta como fallida) | | 🔄 **Procesa async** | Usa colas (Redis, SQS) para procesar el webhook después de responder | | 🔐 **Verifica firma** | Siempre valida el HMAC sobre el cuerpo crudo antes de procesar cualquier evento | | 🆔 **Idempotencia** | Guarda el `id` del sobre (igual a `X-Webhook-Id`); en un reintento llega el mismo `id` | | 📊 **Monitorea** | Revisa el historial de entregas y reintenta las fallidas con el endpoint de reintento | --- # 🏢 Empresa :::warning Autenticación Requerida Incluir en todos los endpoints: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Agrega `?client_uuid={{client_uuid}}` para operar sobre una empresa cliente (Casa de Software) en los endpoints de **configuración** y **datos de la empresa**. - El `client_uuid` de un cliente es el campo `uuid` que devuelve `GET /company/customers` o el alta del cliente (`dataRecords.uuid`). - Solo funciona con la relación **activa**. Con la relación deshabilitada responde `403` con `Acceso no autorizado. Acceso denegado: No tiene permisos para operar sobre esta empresa o la relación no está activa.` - Si envías el `id` numérico en lugar del `uuid`, responde `500` con `Error interno del servidor. Debe enviar el UUID del cliente, no el ID.` Un `uuid` que no existe responde `404` con `Empresa no encontrada.` ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: --- ## 🔧 Configuración de la Empresa **GET /company/settings — Obtener Configuración** ```http GET {{url}}/company/settings?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve las configuraciones generales de la empresa. Si a la empresa le falta alguna configuración, la API la crea con su valor por defecto antes de responder. Solo se listan las configuraciones vigentes, ordenadas por grupo (`setting.tag`). **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "settings": [ { "id": 5012, "company_id": 1262, "setting_id": 1, "value": "0", "active": 1, "setting": { "id": 1, "tag": 1, "key_value": "SENDMAILCOPY", "data_type": "B", "value": "0", "description": "Enviar copia del correo", "active": 1 } } ], "success": true } ``` Usa el `id` de cada elemento de `settings` (no el de `setting`) para actualizarlo con `PUT /company/settings`. **PUT /company/settings — Actualizar Configuración** ```http PUT {{url}}/company/settings?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` El body lleva `records`: un **arreglo serializado como texto JSON**. Cada elemento indica el `id` de la configuración (el `id` que devuelve `GET /company/settings` en cada elemento de `settings`) y su nuevo `value`. | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `records` | string (JSON) | ✅ | Arreglo de objetos `{ "id": ..., "value": ... }` serializado como texto. | | `records[].id` | integer | ✅ | `id` de la configuración de la empresa. | | `records[].value` | string | ✅ | Nuevo valor. | ```json { "records": "[{\"id\":5012,\"value\":\"1\"},{\"id\":5013,\"value\":\"facturacion@empresaejemplo.com\"}]" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Configuración actualizada", "success": true } ``` **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` o no es texto JSON. Un body plano (sin `records`) responde este error. | | `401` | `Unauthenticated.` | Token ausente o inválido. | | `403` | `Acceso no autorizado. ...` | Empresa inactiva o relación con el cliente no activa. | --- ## 🏢 Datos de la Empresa **GET /company — Obtener Información de la Empresa** ```http GET {{url}}/company?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve tu empresa o, con `client_uuid`, la del cliente. Es la forma de obtener el **`uuid` de tu propia empresa**, que exigen `PUT /company/{uuid}`, `PUT /company/{uuid}/image` y `POST /company/{uuid}/customer`. Toda empresa recibe su `uuid` al crearse. **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "data": { "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "country_id": 45, "city_id": 149, "identity_document_id": 3, "type_organization_id": 1, "tax_regime_id": 1, "tax_level_id": 5, "company_name": "EMPRESA EJEMPLO S.A.S", "trade_name": "Empresa Ejemplo", "dni": "900123456", "dv": 8, "address": "Calle 123 # 45-67", "email": "facturacion@empresaejemplo.com", "full_path_image": "" } }, "total": 1, "success": true } ``` La respuesta no incluye el `id` numérico de la empresa. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `403` | `Acceso no autorizado. La empresa se encuentra inactiva.` | La empresa resuelta está inactiva. | | `403` | `Acceso no autorizado. Acceso denegado: No tiene permisos para operar sobre esta empresa o la relación no está activa.` | El `client_uuid` no es un cliente tuyo o la relación está deshabilitada. | | `403` | `Usuario no asociado a ninguna empresa.` | El usuario del token no tiene empresa. | | `404` | `Empresa no encontrada.` | El `client_uuid` no existe. | | `500` | `Error interno del servidor. Debe enviar el UUID del cliente, no el ID.` | Se envió el `id` numérico como `client_uuid`. | **PUT /company/{uuid} — Actualizar Empresa** ```http PUT {{url}}/company/{uuid}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` de la empresa que se actualiza: la tuya o, si envías `client_uuid`, la del cliente. Si no coincide, responde `404` con `La empresa no existe.` | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | :::info Formato del payload `records` Los datos de la empresa se encapsulan dentro de `records`, serializado como texto JSON. Solo se guardan los campos cuyo valor cambia. ::: **Campos de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `country_id` | integer | ✅ | ID del país (`45` Colombia). Debe venir siempre. Si es el número `45` (no el texto `"45"`), la API recalcula el `dv` a partir de `dni`. | | `dni` | string | Si `country_id` es `45` | NIT sin dígito de verificación. Solo se usa para recalcular el `dv`: **el NIT no se actualiza**. | | `city_id` | integer | No | ID de la ciudad/municipio DIAN | | `identity_document_id` | integer | No | Tipo de documento (`3` NIT, `1` Cédula) | | `type_organization_id` | integer | No | `1` Jurídica, `2` Natural | | `tax_regime_id` | integer | No | `1` Responsable IVA, `2` No responsable | | `tax_level_id` | integer | No | `4` No aplica, `5` R-99-PN | | `company_name` | string | No | Razón social o nombre completo | | `trade_name` | string | No | Nombre comercial | | `address` | string | No | Dirección fiscal completa | | `city_name` | string | No | Nombre de la ciudad | | `merchant_registration` | string | No | Matrícula mercantil | | `location` | string | No | Barrio / ubicación complementaria | | `postal_code` | string | No | Código postal | | `mobile` | string | No | Teléfono móvil | | `phone` | string | No | Teléfono fijo | | `email` | string | No | Correo de notificaciones | | `web` | string | No | Sitio web corporativo | | `extra_data` | string | No | Información adicional | | `imgdata` | string | No | Logo en Base64 con prefijo Data URI. Si trae contenido, `imgname` es obligatorio. `imgdata: ""` se ignora. | | `imgname` | string | Con `imgdata` | Nombre del archivo del logo. Solo se usa su extensión (ver [Actualizar Logo](https://docs.matias-api.com/docs/endpoints/company#actualizar-logo)). | El `dv` que envíes se sobrescribe. Cualquier otro campo (por ejemplo `active`, `uuid` o `id`) se ignora: el estado de la empresa no se cambia por este endpoint. **JavaScript / Node.js** ```javascript const records = { country_id: 45, city_id: 149, identity_document_id: 3, type_organization_id: 1, tax_regime_id: 1, tax_level_id: 5, company_name: "EMPRESA EJEMPLO S.A.S", trade_name: "Empresa Ejemplo", dni: "900123456", address: "Calle 123 # 45-67", merchant_registration: "000000", location: "", postal_code: "110111", mobile: "3001234567", phone: "6012345678", email: "facturacion@empresaejemplo.com", web: "https://empresaejemplo.com/", imgdata: "", imgname: "" // Opcional: logo en Base64 + nombre del archivo (ej. "logo.png") }; const response = await axios.put(`${url}/company/${uuid}`, { records: JSON.stringify(records) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP (cURL)** ```php $records = [ 'country_id' => 45, 'city_id' => 149, 'identity_document_id' => 3, 'type_organization_id' => 1, 'tax_regime_id' => 1, 'tax_level_id' => 5, 'company_name' => 'EMPRESA EJEMPLO S.A.S', 'dni' => '900123456', 'address' => 'Calle 123 # 45-67', 'email' => 'facturacion@empresaejemplo.com', // Opcional: 'imgdata' => 'data:image/png;base64,...', 'imgname' => 'logo.png', ]; $payload = json_encode(['records' => json_encode($records)]); $ch = curl_init("{$url}/company/{$uuid}"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'], ]); curl_exec($ch); ``` **Python** ```python records = { "country_id": 45, "city_id": 149, "identity_document_id": 3, "type_organization_id": 1, "tax_regime_id": 1, "tax_level_id": 5, "company_name": "EMPRESA EJEMPLO S.A.S", "dni": "900123456", "address": "Calle 123 # 45-67", "email": "facturacion@empresaejemplo.com", # Opcional: "imgdata": "data:image/png;base64,...", "imgname": "logo.png", } response = requests.put(f"{url}/company/{uuid}", json={"records": json.dumps(records)}, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}) ``` **Postman (Pre-request)** ```javascript const records = { country_id: 45, city_id: 149, identity_document_id: 3, type_organization_id: 1, tax_regime_id: 1, tax_level_id: 5, company_name: "EMPRESA EJEMPLO S.A.S", trade_name: "Empresa Ejemplo", dni: "900123456", address: "Calle 123 # 45-67", merchant_registration: "000000", location: "", postal_code: "110111", mobile: "3001234567", phone: "6012345678", email: "facturacion@empresaejemplo.com", web: "https://empresaejemplo.com/", imgdata: "", imgname: "", }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "country_id": 45, "city_id": 149, "company_name": "EMPRESA EJEMPLO S.A.S", "dni": "900123456", "dv": 8, "address": "Calle 123 # 45-67", "email": "facturacion@empresaejemplo.com", "full_path_image": "" }, "message": "Registro actualizado correctamente.", "success": true } ``` `dataRecords` es la empresa actualizada, sin el `id` numérico. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` o no es texto JSON. | | `404` | `La empresa no existe.` | El `{uuid}` de la ruta no es el de la empresa que se actualiza. | | `400` | Mensaje de la base de datos | Un valor viola una restricción de integridad (por ejemplo, un `city_id` inexistente). | | `500` | `Error interno del servidor. La propiedad RECORDS no está definida.` | `records` decodifica a un valor vacío. | | `500` | `Error interno del servidor. ...` | Falta `country_id`, falta `dni` con `country_id` `45`, o falta `imgname` con `imgdata`. | Además, los errores de `client_uuid` descritos en [Obtener Información de la Empresa](https://docs.matias-api.com/docs/endpoints/company#datos-empresa). **PUT /company/{uuid}/image — Actualizar Logo** ```http PUT {{url}}/company/{uuid}/image?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` :::info Logo en Base64 Envía dentro de `records` (texto JSON) dos propiedades: - `imgdata`: el logo en Base64 con prefijo Data URI (`data:image/png;base64,...`). Se toma lo que sigue a la primera coma. - `imgname`: nombre del archivo, **obligatorio**. Solo se usa su extensión: `png`, `jpg`, `jpeg`, `gif`, `webp`, `svg`, `jfif`, `bmp` o `ico`. Cualquier otra, o ninguna, se guarda como `png`. El nombre final del archivo lo asigna la API. ::: **JavaScript / Node.js** ```javascript const imgBuffer = fs.readFileSync('logo.png'); const base64Img = 'data:image/png;base64,' + imgBuffer.toString('base64'); await axios.put(`${url}/company/${uuid}/image`, { records: JSON.stringify({ imgdata: base64Img, imgname: 'logo.png' }) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP** ```php $base64Img = 'data:image/png;base64,' . base64_encode(file_get_contents('logo.png')); $payload = json_encode(['records' => json_encode(['imgdata' => $base64Img, 'imgname' => 'logo.png'])]); $ch = curl_init("{$url}/company/{$uuid}/image"); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json']]); curl_exec($ch); ``` **Python** ```python with open("logo.png", "rb") as f: b64_img = "data:image/png;base64," + base64.b64encode(f.read()).decode("utf-8") requests.put(f"{url}/company/{uuid}/image", json={"records": json.dumps({"imgdata": b64_img, "imgname": "logo.png"})}, headers={"Authorization": f"Bearer {token}"}) ``` **Postman (Pre-request)** ```javascript const payload = { records: JSON.stringify({ imgdata: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", imgname: "logo.png" }) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "company_name": "EMPRESA EJEMPLO S.A.S", "image": "/storage/companies/1262/logo/7c1e9a52-0b3d-4f6e-a8d1-93b2c4e5f607.png", "full_path_image": "https://sandbox-api.matias-api.com/storage/companies/1262/logo/7c1e9a52-0b3d-4f6e-a8d1-93b2c4e5f607.png" }, "message": "Imagen actualizada correctamente.", "success": true } ``` **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `404` | `La empresa no existe.` | El `{uuid}` de la ruta no es el de la empresa que se actualiza. | | `404` | `La propiedad IMGDATA no está definida.` | `records` no trae `imgdata`. | | `404` | `La propiedad IMGDATA no está definida o está vacía.` | `imgdata` no trae contenido después de la coma. | | `500` | `Error interno del servidor. La propiedad RECORDS no está definida.` | `records` ausente o no es JSON válido (este endpoint no responde `422`). | | `500` | `Error interno del servidor. ...` | Falta `imgname`. | Además, los errores de `client_uuid` descritos en [Obtener Información de la Empresa](https://docs.matias-api.com/docs/endpoints/company#datos-empresa). --- ## 👥 Gestión de Clientes (Casa de Software) Una Casa de Software tiene una **cuenta principal** y, debajo de ella, crea las empresas de sus clientes como **sub-cuentas**. Cada sub-cuenta queda vinculada a la Casa de Software mediante una **relación**, que puede estar activa o deshabilitada. | | Cuenta principal | Sub-cuenta (cliente) | |---|---|---| | **Endpoint** | `POST /register` (público, sin token) | `POST /company/{uuid}/customer` (con el token de la Casa de Software) | | **Tipo de usuario** | `DEVELOPER`, lo fija el servidor | `CLIENT`, lo fija el servidor | | **Correo** | Se envía un correo de verificación; hay que verificarlo antes de iniciar sesión | No se envía ningún correo; queda verificado y el cliente puede iniciar sesión de inmediato | | **Vínculo** | Ninguno | Relación activa con la Casa de Software | | **Respuesta** | Solo `message` y `success` | `dataRecords` con la empresa creada, incluidos su `uuid` y su `id` | :::danger `POST /register` no crea sub-cuentas `POST /register` es el auto-registro de una cuenta principal. Si envías `company_id`, responde `422` y no crea nada. Las sub-cuentas se crean **solo** con `POST /company/{uuid}/customer` y el token de la Casa de Software. ::: :::danger No envíes `client_uuid` ni `companyId` en los endpoints de clientes En los endpoints de esta sección quien actúa es siempre la **Casa de Software**, y el cliente se identifica con el `{uuid}` de la ruta. En el listado, las estadísticas, la deshabilitación y la habilitación, si envías `client_uuid` o `companyId` la API toma esa empresa como la que actúa, y la operación falla o se aplica sobre otra empresa. En la creación se ignora, y en los dos `PUT` de clientes el servidor lo reemplaza por el `{uuid}` de la ruta. ::: **GET /company/customers — Listar Clientes** ```http GET {{url}}/company/customers?is_active=1&search=900123456 Authorization: Bearer {token} ``` Devuelve tus clientes paginados (15 por página), ordenados por razón social. | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `is_active` | query | No | `1` (default) clientes con relación activa, `0` clientes deshabilitados. | | `search` | query | No | Coincidencia parcial en NIT (`dni`) o razón social (`company_name`). | | `uuid` | query | No | Filtra por el `uuid` exacto de un cliente. | | `page` | query | No | Página. El tamaño de página es fijo: 15. | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "current_page": 1, "data": [ { "id": 1262, "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "dni": "900123456", "dv": 8, "company_name": "CLIENTE EJEMPLO S.A.S", "trade_name": null, "email": "facturacion@clienteejemplo.com", "active": 1, "full_path_image": "", "quotas": [], "company": { "id": 845, "company_id": 37, "customer_id": 1262, "active": 1 } } ], "first_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/company/customers?page=1", "from": 1, "last_page": 1, "last_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/company/customers?page=1", "links": [], "next_page_url": null, "path": "https://sandbox-api.matias-api.com/api/ubl2.1/company/customers", "per_page": 15, "prev_page_url": null, "to": 1, "total": 1 }, "success": true } ``` Cada elemento de `data` es la empresa cliente: | Campo | Contenido | |-------|-----------| | `id` | `id` numérico interno del cliente. | | `uuid` | Identificador del cliente para `/company/customers/{uuid}...` y para `client_uuid` en el resto de la API, incluidas las cuotas (`POST /memberships/quotas`). | | `software`, `certificate`, `resolutions` | Software, certificado y resoluciones activas del cliente. `certificate` no incluye la contraseña, la ruta del archivo, el contenido del certificado ni `company_id`. | | `country`, `city`, `identity_document`, `type_organization` | Catálogos asociados. | | `quotas` | Cuotas del cliente agrupadas por suscripción, de la más reciente a la más antigua. Cada elemento trae `subscription_id`, y en `assigned_documents` y `consumed_documents` la suma de las cuotas de esa suscripción. Los demás campos (`id`, `uuid`, `start_date`, `end_date`) son los de una de las cuotas del grupo. | | `company` | Relación con la Casa de Software (`company_id`, `customer_id`, `active`) y, anidada, tu empresa. | :::warning No confundas los IDs `data[i].company.id` es el ID de la relación Casa de Software ↔ cliente, no el del cliente. El estado de la relación está en `data[i].company.active`. ::: **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `403` | `La empresa se encuentra inactiva.` | Tu empresa está inactiva. La respuesta solo trae `message`. | | `500` | `Server Error` | El usuario del token no tiene empresa asociada, u otro error interno. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | **POST /company/{uuid}/customer — Crear Cliente (sub-cuenta)** ```http POST {{url}}/company/{uuid}/customer Authorization: Bearer {token} Content-Type: application/json Accept: application/json ``` Crea el usuario y la empresa del cliente y los vincula a tu cuenta principal. Todo ocurre en una sola transacción: si algo falla, no se crea nada. | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` de **tu propia empresa** (cuenta principal), no el de un cliente. Lo obtienes con `GET /company`. | **Requisitos:** el titular de tu empresa es una Casa de Software (usuario `DEVELOPER`), tu empresa está activa y, en producción, tu plan no es FREE (en sandbox esta restricción no aplica). | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `first_name` | string | ✅ | Nombre del usuario del cliente. Máx. 100. Se guarda en mayúsculas. | | `last_name` | string | ✅ | Apellido del usuario del cliente. Máx. 100. Se guarda en mayúsculas. | | `company_name` | string | ✅ | Razón social. Máx. 100. Se guarda en mayúsculas. | | `trade_name` | string | No | Nombre comercial. Máx. 250. Se guarda tal como llega. | | `email` | string | ✅ | Correo del usuario y de la empresa. Máx. 255. Debe ser válido, con dominio existente, y no estar registrado. | | `password` | string | ✅ | Mínimo 8 caracteres. | | `password_confirmation` | string | ✅ | Debe coincidir con `password`. | | `dni` | string | ✅ | NIT **solo dígitos** (sin `dv`, puntos ni guiones). Máx. 15. No debe estar registrado. El `dv` lo calcula la API. | | `country_id` | integer | No | Default `45`. | | `city_id` | integer | No | Default `149`. | | `identity_document_id` | integer | No | Default `3`. | | `type_organization_id` | integer | No | Default `1`. | | `tax_regime_id` | integer | No | Default `1`. | | `tax_level_id` | integer | No | Default `5`. | | `address`, `location`, `postal_code`, `mobile`, `phone`, `web` | string | No | Default vacío. | Si envías `type_id`, se ignora: el usuario del cliente siempre es `CLIENT`. ```json { "first_name": "Juan", "last_name": "Pérez", "company_name": "Cliente Ejemplo S.A.S", "email": "facturacion@clienteejemplo.com", "password": "ClaveSegura2026", "password_confirmation": "ClaveSegura2026", "dni": "900123456", "country_id": 45, "city_id": 149, "address": "Calle 123 # 45-67", "mobile": "3001234567" } ``` **Qué crea:** un usuario `CLIENT` activo con el correo ya verificado (no se le envía ningún correo), la empresa del cliente con su `uuid` y su `dv`, una suscripción FREE de 100 documentos por 1 año a nombre del cliente (también en sandbox) y la relación activa con tu empresa. No crea cuotas. Esa suscripción FREE no limita la emisión del cliente mientras está vinculado a tu empresa: cada documento que emite se descuenta de tu suscripción activa. Si le asignas una cuota vigente, el cliente consume de esa cuota; si no, consume de tu bolsa compartida. Ver [Guía rápida: cuotas para clientes](https://docs.matias-api.com/docs/endpoints/memberships-health#guia-cuotas). **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "id": 1262, "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "country_id": 45, "city_id": 149, "identity_document_id": 3, "type_organization_id": 1, "tax_regime_id": 1, "tax_level_id": 5, "company_name": "CLIENTE EJEMPLO S.A.S", "trade_name": null, "dni": "900123456", "dv": 8, "address": "Calle 123 # 45-67", "location": "", "postal_code": "", "mobile": "3001234567", "phone": "", "email": "facturacion@clienteejemplo.com", "web": "", "full_path_image": "" }, "message": "Cliente creado con éxito, ya puede iniciar sesión. No es necesario verificar el correo electrónico.", "success": true } ``` La respuesta es `200`, no `201`. Guarda `dataRecords.uuid`: es el identificador del cliente para `/company/customers/{uuid}...` y el `client_uuid` del resto de la API, incluidas las [cuotas](https://docs.matias-api.com/docs/endpoints/memberships-health#guia-cuotas). `dataRecords.id` es el `id` interno. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `422` | `El nombre es requerido` · `El apellido es requerido` · `El nombre de la empresa es requerido` | Falta `first_name`, `last_name` o `company_name`. | | `422` | `El correo electrónico es requerido` · `El correo electrónico no es válido o su dominio no existe` · `El correo electrónico ya existe` | `email` ausente, inválido o ya registrado. | | `422` | `El NIT es requerido` · `El NIT ya existe` | `dni` ausente o ya registrado. | | `422` | `El NIT debe contener solo dígitos, sin puntos, guiones ni dígito de verificación` | `dni` con caracteres que no son dígitos. | | `422` | `El NIT no puede tener más de 15 dígitos` | `dni` de más de 15 dígitos. | | `422` | Mensaje estándar | `password` de menos de 8 caracteres o `password_confirmation` que no coincide. | | `403` | `Acceso no autorizado. Acceso denegado: Solo puede crear clientes en su propia empresa.` | El `{uuid}` de la ruta no es el de tu empresa. | | `403` | `Acceso no autorizado. La empresa se encuentra inactiva.` | Tu empresa está inactiva. | | `403` | `Usuario no asociado a ninguna empresa.` | El usuario del token, o el titular de tu empresa, no tiene empresa. | | `403` | `El plan FREE no permite agregar clientes. Actualice su plan para acceder a esta funcionalidad.` | Tu plan es FREE (solo en producción). | | `403` | `Solo los usuarios DESARROLLADORES pueden crear clientes.` | El titular de tu empresa no es una Casa de Software. | | `404` | `Empresa no encontrada.` | El `{uuid}` no existe. | | `404` | `Usuario no encontrado.` | El usuario titular de tu empresa no existe. | | `400` | Mensaje de la base de datos | Un valor viola una restricción de integridad (por ejemplo, un `country_id` o `city_id` inexistente). | | `500` | `Error interno del servidor. ...` | Cualquier otro error al guardar. Incluye `reference` para soporte. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | La validación del body (`422`) ocurre **antes** de comprobar el `{uuid}`. Los `403`, `404` y `400` traen la forma `{"success": false, "message": "..."}`; con cualquiera de ellos no se crea nada. ```json { "message": "El correo electrónico ya existe (and 1 more error)", "errors": { "email": ["El correo electrónico ya existe"], "dni": ["El NIT ya existe"] } } ``` **PUT /company/customers/{client_uuid} — Actualizar Datos del Cliente** ```http PUT {{url}}/company/customers/{client_uuid} Authorization: Bearer {token} Content-Type: application/json ``` | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `client_uuid` | path | ✅ Sí | `uuid` del cliente. La relación con el cliente debe estar activa. | El body lleva `records`: los datos **serializados como texto JSON**, con los mismos campos que [Actualizar Empresa](https://docs.matias-api.com/docs/endpoints/company#datos-empresa). `country_id` es obligatorio; si es el número `45`, también `dni` (solo para recalcular el `dv`: el NIT no se actualiza). Opcionalmente, `imgdata` + `imgname` para cambiar el logo. Los campos no admitidos se ignoran y solo se guardan los valores que cambian. ```json { "records": "{\"country_id\":45,\"dni\":\"900123456\",\"city_id\":149,\"address\":\"Carrera 7 # 10-20\",\"phone\":\"6012345678\"}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "country_id": 45, "city_id": 149, "company_name": "CLIENTE EJEMPLO S.A.S", "dni": "900123456", "dv": 8, "address": "Carrera 7 # 10-20", "phone": "6012345678", "email": "facturacion@clienteejemplo.com", "full_path_image": "" }, "message": "Registro actualizado correctamente.", "success": true } ``` **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` (por ejemplo, un body plano) o no es texto JSON. | | `403` | `Acceso no autorizado. Acceso denegado: No tiene permisos para operar sobre esta empresa o la relación no está activa.` | El cliente no es tuyo o la relación está deshabilitada. | | `403` | `Acceso no autorizado. La empresa se encuentra inactiva.` | La empresa del cliente está inactiva. | | `403` | `Usuario no asociado a ninguna empresa.` | El usuario del token no tiene empresa. | | `404` | `Empresa no encontrada.` | El `uuid` no existe. | | `400` | Mensaje de la base de datos | Un valor viola una restricción de integridad. | | `500` | `Error interno del servidor. Debe enviar el UUID del cliente, no el ID.` | Se envió el `id` numérico en la ruta. | | `500` | `Error interno del servidor. La propiedad RECORDS no está definida.` | `records` decodifica a un valor vacío. | | `500` | `Error interno del servidor. ...` | Falta `country_id`, falta `dni` con `country_id` `45`, o falta `imgname` con `imgdata`. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | **PUT /company/customers/{client_uuid}/image — Actualizar Imagen del Cliente** ```http PUT {{url}}/company/customers/{client_uuid}/image Authorization: Bearer {token} Content-Type: application/json ``` Permite a una Casa de Software actualizar el logo de una empresa cliente. La relación con el cliente debe estar activa. `records` lleva `imgdata` e `imgname`, con las mismas reglas que [Actualizar Logo](https://docs.matias-api.com/docs/endpoints/company#actualizar-logo). ```json { "records": "{\"imgdata\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...\",\"imgname\":\"logo.png\"}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "uuid": "1f3a0b11-b698-11f1-a6c7-d843ae899220", "company_name": "CLIENTE EJEMPLO S.A.S", "image": "/storage/companies/1262/logo/7c1e9a52-0b3d-4f6e-a8d1-93b2c4e5f607.png", "full_path_image": "https://sandbox-api.matias-api.com/storage/companies/1262/logo/7c1e9a52-0b3d-4f6e-a8d1-93b2c4e5f607.png" }, "message": "Imagen actualizada correctamente.", "success": true } ``` **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `403` | `Acceso no autorizado. Acceso denegado: No tiene permisos para operar sobre esta empresa o la relación no está activa.` | El cliente no es tuyo o la relación está deshabilitada. | | `403` | `Acceso no autorizado. La empresa se encuentra inactiva.` | La empresa del cliente está inactiva. | | `403` | `Usuario no asociado a ninguna empresa.` | El usuario del token no tiene empresa. La respuesta trae la forma `{"success": false, "message": "..."}`. | | `404` | `Empresa no encontrada.` | El `uuid` no existe. | | `404` | `La propiedad IMGDATA no está definida.` | `records` no trae `imgdata`. | | `404` | `La propiedad IMGDATA no está definida o está vacía.` | `imgdata` no trae contenido después de la coma. | | `500` | `Error interno del servidor. La propiedad RECORDS no está definida.` | `records` ausente o no es JSON válido. | | `500` | `Error interno del servidor. Debe enviar el UUID del cliente, no el ID.` | Se envió el `id` numérico en la ruta. | | `500` | `Error interno del servidor. ...` | Falta `imgname`. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | **GET /company/customers/{uuid}/stats — Estadísticas del Cliente** ```http GET {{url}}/company/customers/{uuid}/stats Authorization: Bearer {token} ``` Consolida el consumo de documentos, el almacenamiento de XML y la membresía con la que opera el cliente. Funciona también con clientes deshabilitados. | Parámetro | Ubicación | Requerido | Descripción | |-----------|-----------|-----------|-------------| | `uuid` | path | ✅ Sí | `uuid` del cliente. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "customer": { "id": 1262, "dni": "900123456", "dv": 8, "company_name": "CLIENTE EJEMPLO S.A.S", "email": "facturacion@clienteejemplo.com", "city": "Bogotá D.C." }, "stats": { "total_documents_consumed": 148, "total_storage_bytes": 5242880, "total_storage_mb": 5.0 }, "subscription": { "id": 311, "status": "active", "start_date": "2026-10-14T05:00:00.000000Z", "end_date": "2027-10-13T05:00:00.000000Z", "total_documents_limit": 500, "plan_name": "Cuota Asignada", "is_unlimited": false } }, "success": true } ``` | Campo | Descripción | |-------|-------------| | `customer.city` | Nombre de la ciudad, o `null`. | | `stats.total_documents_consumed` | Documentos consumidos por el cliente, sumando todos sus periodos. | | `stats.total_storage_bytes` | Bytes de los XML del cliente ya almacenados. | | `stats.total_storage_mb` | Lo mismo en MB, con 2 decimales. | | `subscription` | Membresía con la que opera el cliente. Es `null` si la relación está activa y tu empresa no tiene suscripción activa, o si la relación no está activa y el cliente no tiene una suscripción propia activa. | | Caso | `plan_name` | `total_documents_limit` | |------|-------------|-------------------------| | Relación activa y el cliente tiene una cuota vigente (hoy entre su `start_date` y su `end_date`) en la suscripción activa con la que opera tu empresa | `Cuota Asignada` | `assigned_documents` de la cuota | | Relación activa y el cliente consume de tu bolsa, sin cuota vigente en esa suscripción | `CUENTA MAESTRA` | `0` (no expone el límite de tu plan) | | Relación no activa (por ejemplo, cliente deshabilitado): el cliente opera con su propia suscripción | Nombre de su plan | Documentos de su suscripción | Con la relación activa, `subscription.id`, `status`, `start_date`, `end_date` e `is_unlimited` son los de **tu** suscripción activa, no los del cliente: solo cambian `plan_name` y `total_documents_limit`. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `403` | `La empresa se encuentra inactiva.` | Tu empresa está inactiva. | | `404` | `Cliente no encontrado.` | El `uuid` no existe o no es un cliente tuyo. | | `500` | `Error interno del servidor.` | Cualquier otro error, incluido un usuario sin empresa asociada. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | ```json { "message": "Cliente no encontrado.", "success": false } ``` **POST /company/customers/{client_uuid}/enable — Habilitar Cliente** ```http POST {{url}}/company/customers/{client_uuid}/enable Authorization: Bearer {token} ``` Vuelve a activar la relación con un cliente **tuyo**. Si la empresa del cliente estaba inactiva, también la reactiva. No lleva body. **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Cliente habilitado correctamente.", "success": true } ``` Si el cliente ya estaba habilitado, la respuesta también es `200`. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `404` | `El cliente no existe.` | El `uuid` no existe o no es un cliente tuyo. No se modifica nada. | | `403` | `Acceso no autorizado. La empresa se encuentra inactiva.` | Tu empresa está inactiva. | | `403` | `Usuario no asociado a ninguna empresa.` | El usuario del token no tiene empresa. | | `500` | `Error interno del servidor. ...` | Error interno. Incluye `reference`. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | ```json { "success": false, "message": "El cliente no existe." } ``` **DELETE /company/customers/{client_uuid} — Deshabilitar Cliente** ```http DELETE {{url}}/company/customers/{client_uuid} Authorization: Bearer {token} ``` :::warning Acción reversible Deshabilita la **relación** entre tu Casa de Software y el cliente. Es un borrado lógico: no elimina la empresa, el usuario ni la suscripción del cliente, y no cambia el estado de la empresa del cliente. Se puede revertir con `POST .../enable`. ::: Desde ese momento: - El cliente sale del listado por defecto y aparece al consultar con `is_active=0`. - Ya no puedes operar en su nombre con `client_uuid` ni actualizar sus datos o su logo (`403`). **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Cliente eliminado correctamente.", "success": true } ``` Si el cliente ya estaba deshabilitado, la respuesta también es `200`. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `401` | `Unauthenticated.` | Token ausente o inválido. | | `404` | `El cliente no existe.` | El `uuid` no existe o no es un cliente tuyo. No se modifica nada. | | `403` | `Acceso no autorizado. La empresa se encuentra inactiva.` | Tu empresa está inactiva. | | `403` | `Usuario no asociado a ninguna empresa.` | El usuario del token no tiene empresa. | | `500` | `Error interno del servidor. ...` | Error interno. Incluye `reference`. | | `429` | `Too Many Attempts.` | Se superó el límite de solicitudes. | --- # 👤 Perfil :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes actualizar el perfil del usuario de una de tus sub-cuentas agregando `?client_uuid={{client_uuid}}` en los endpoints `PUT`. `GET /profile` y `GET /profile/types` no lo usan: siempre responden sobre el usuario del token. ::: --- ## 👤 Gestión del Perfil **GET /profile — Obtener Datos del Usuario** ```http GET {{url}}/profile Authorization: Bearer {token} Accept: application/json ``` **Descripción:** Retorna la información del usuario dueño del token y su tipo de usuario. Es equivalente a `GET /auth/user`. **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "id": 1, "type_id": 2, "first_name": "JUAN", "last_name": "PEREZ", "email": "usuario@miempresa.com", "avatar": "users/1/profile/avatar.png", "active": 1, "name": "JUAN PEREZ", "avatarUrl": "https://sandbox-api.matias-api.com/storage/users/1/profile/avatar.png", "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } } ] }, "success": true } ``` **❌ No Autenticado (HTTP 401)** Requiere `Accept: application/json`; sin esa cabecera, la respuesta es una redirección (HTTP 302). ```json { "message": "Unauthenticated." } ``` **GET /profile/types — Listar Tipos de Usuario** ```http GET {{url}}/profile/types Authorization: Bearer {token} Accept: application/json ``` **Descripción:** Devuelve el catálogo de tipos de usuario, el valor de `type_id` en el perfil y en la respuesta del login. | `type_id` | Tipo | Quién lo tiene | |---|---|---| | `1` | ADMINISTRADOR | Administración de la plataforma | | `2` | DESARROLLADOR | Casa de Software / cuenta principal creada con `POST /register` | | `3` | EMPRESA | No se asigna con `POST /register` ni al crear sub-cuentas | | `4` | USUARIO | No se asigna con `POST /register` ni al crear sub-cuentas | | `5` | CLIENTE | Usuario de una sub-cuenta creada por una Casa de Software (`POST /company/{uuid}/customer`) | **✅ Respuesta Exitosa (HTTP 200)** ```json { "records": [ { "id": 1, "user_type_name": "ADMINISTRADOR", "type": 1, "active": 1 }, { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } ], "success": true } ``` **PUT /profile/{id} — Actualizar Perfil de Usuario** ```http PUT {{url}}/profile/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json Accept: application/json ``` :::caution Problema conocido A la fecha, este endpoint valida la petición y responde `200`, pero **no guarda** los cambios de nombre, apellido ni avatar. No lo uses todavía para actualizar el perfil: para cambiar la imagen, usa [`PUT /profile/{id}/image`](https://docs.matias-api.com/docs/endpoints/profile#actualizar-imagen), y para el correo, [`PUT /profile/update-email`](https://docs.matias-api.com/docs/endpoints/profile#actualizar-correo). ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del usuario. Debe ser el usuario de la empresa del token (o de la sub-cuenta indicada en `client_uuid`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | | `records` | body | ✅ Sí | Cadena JSON (`stringified JSON`) con los campos a cambiar. | **Campos dentro de `records`:** | Campo | Tipo | Descripción | |---|---|---| | `first_name` | `string` | Nombre | | `last_name` | `string` | Apellido | | `imgdata` | `string` | Opcional. Imagen del avatar en Base64 con prefijo Data URI | | `imgname` | `string` | Nombre del archivo de la imagen. Obligatorio si se envía `imgdata` | Cualquier otro campo se ignora. **El correo no se cambia aquí**: usa [`PUT /profile/update-email`](https://docs.matias-api.com/docs/endpoints/profile#actualizar-correo). **Body (JSON):** ```json { "records": "{\"first_name\":\"JUAN\",\"last_name\":\"PEREZ\"}" } ``` **✅ Respuesta (HTTP 200)** Devuelve el usuario **tal como estaba antes** de la petición. ```json { "dataRecords": { "data": { "id": 1, "type_id": 2, "first_name": "JUAN", "last_name": "PEREZ", "email": "usuario@miempresa.com", "avatar": "users/1/profile/avatar.png", "active": 1, "name": "JUAN PEREZ", "avatarUrl": "https://sandbox-api.matias-api.com/storage/users/1/profile/avatar.png", "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } } }, "success": true } ``` **❌ El ID no Corresponde al Usuario (HTTP 400)** ```json { "message": "El usuario autenticado no coincide con el ID proporcionado", "success": false } ``` **❌ Falta `records` (HTTP 422)** Se recibe si se envían los campos sueltos en el body en lugar de dentro de `records`. ```json { "message": "El campo records es requerido", "errors": { "records": [ "El campo records es requerido" ] } } ``` Si `records` no es un JSON válido, el mensaje es `El campo records debe ser un JSON válido`. **PUT /profile/update-email — Actualizar Correo Electrónico** ```http PUT {{url}}/profile/update-email?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json Accept: application/json ``` **Descripción:** Cambia el correo electrónico del usuario. El nuevo correo no puede estar en uso por otro usuario. La API envía automáticamente un correo de verificación a la nueva dirección. :::warning El correo queda sin verificar Tras el cambio, el correo queda **sin verificar**: hasta abrir el enlace enviado a la nueva dirección, `POST /auth/login` responde **HTTP 404** con `El correo electrónico no ha sido verificado. Comuníquese con el administrador.` Los tokens ya emitidos siguen funcionando. Si el correo no llega, pide uno nuevo con [`POST /email/verification-notification`](https://docs.matias-api.com/docs/endpoints/intro-auth#verificacion-correo). ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Casa de Software). | | `email` | body | ✅ Sí | Nuevo correo (formato válido, máx. 255, no usado por otro usuario). | **Body (JSON):** ```json { "email": "nuevo-correo@miempresa.com" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Correo electrónico actualizado exitosamente. Se ha enviado un correo de verificación a tu nuevo email.", "dataRecords": { "data": { "email": "nuevo-correo@miempresa.com", "email_verified_at": null, "previous_email": "usuario@miempresa.com", "verification_sent": true } }, "success": true } ``` **❌ Error de Validación (HTTP 422)** ```json { "message": "Validación fallida", "errors": { "email": [ "El correo electrónico ya está en uso, por favor elige otro correo electrónico. Si este es tu correo electrónico, por favor inicia sesión con él." ] }, "success": false } ``` Otros mensajes de `errors.email`: `El correo electrónico es obligatorio`, `El correo electrónico debe ser una dirección válida` y `El correo electrónico no puede exceder 255 caracteres`. **PUT /profile/{id}/image — Actualizar Imagen de Perfil** ```http PUT {{url}}/profile/{id}/image?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json Accept: application/json ``` **Descripción:** Permite actualizar el avatar del usuario enviando la imagen en Base64. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del usuario. Debe ser el usuario de la empresa del token (o de la sub-cuenta indicada en `client_uuid`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | | `records` | body | ✅ Sí | Cadena JSON (`stringified JSON`) con los campos de la imagen. | **Campos dentro de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `id` | `integer` | ✅ Sí | El mismo ID del usuario del path. Sin él, la imagen se sube pero el avatar del perfil no cambia | | `imgdata` | `string` | ✅ Sí | Imagen codificada en Base64 con prefijo Data URI (`data:image/png;base64,...`) | | `imgname` | `string` | ✅ Sí | Nombre del archivo, con extensión (por ejemplo, `avatar.png`) | **Body (JSON):** ```json { "records": "{\"id\":1,\"imgname\":\"avatar.png\",\"imgdata\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...\"}" } ``` :::info 💡 Guía para Desarrolladores — Imagen en Base64 Envía la imagen del avatar codificada en Base64 con prefijo Data URI en el campo `imgdata`, junto con `id` e `imgname`, dentro de `records`. ::: **JavaScript / Node.js** ```javascript import fs from 'fs'; import axios from 'axios'; const imgBuffer = fs.readFileSync('avatar.png'); const base64Img = 'data:image/png;base64,' + imgBuffer.toString('base64'); const response = await axios.put(`${url}/profile/${userId}/image`, { records: JSON.stringify({ id: userId, imgname: 'avatar.png', imgdata: base64Img }) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json', 'Accept': 'application/json' } }); ``` **PHP** ```php json_encode([ 'id' => $userId, 'imgname' => 'avatar.png', 'imgdata' => $base64Img, ])]); $ch = curl_init("{$url}/profile/{$userId}/image"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json', 'Accept: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import base64 import json import requests with open("avatar.png", "rb") as f: b64_img = "data:image/png;base64," + base64.b64encode(f.read()).decode("utf-8") response = requests.put( f"{url}/profile/{userId}/image", json={"records": json.dumps({"id": userId, "imgname": "avatar.png", "imgdata": b64_img})}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", "Accept": "application/json" } ) ``` **Postman** ```javascript // Pre-request Script const records = { id: Number(pm.environment.get('user_id')), imgname: "avatar.png", imgdata: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **✅ Respuesta (HTTP 200)** Devuelve el usuario **tal como estaba antes** de la petición. Consulta `GET /profile` para ver el nuevo `avatarUrl`. ```json { "dataRecords": { "data": { "id": 1, "type_id": 2, "first_name": "JUAN", "last_name": "PEREZ", "email": "usuario@miempresa.com", "avatar": "users/1/profile/avatar-anterior.png", "active": 1, "name": "JUAN PEREZ", "avatarUrl": "https://sandbox-api.matias-api.com/storage/users/1/profile/avatar-anterior.png", "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } } }, "success": true } ``` **❌ Imagen Ausente o Vacía (HTTP 400)** Sin `imgdata` (o si `records` no es un JSON válido): ```json { "message": "No se proporcionó la imagen en base64", "success": false } ``` Con `imgdata` vacío (por ejemplo, solo el prefijo Data URI): ```json { "message": "La imagen en base64 está vacía", "success": false } ``` Si `{id}` no es el usuario de la empresa, la respuesta es `400` con `El usuario autenticado no coincide con el ID proporcionado`. Sin `imgname`, la API responde `500`. --- # 📄 Resoluciones DIAN :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes administrar las resoluciones de tus empresas cliente agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: :::tip Tipos de documento (`type_document_id`) El `type_document_id` de una resolución es el mismo que envías al emitir el documento, y con él la API busca la resolución al numerar. Valores habituales: | `type_document_id` | Documento | |---|---| | `7` | Factura electrónica | | `8` | Factura de exportación | | `9` · `10` | Factura por contingencia (facturador · DIAN) | | `4` · `5` | Nota débito · Nota crédito | | `11` | Documento soporte | | `15` | Nota de ajuste del documento soporte | | `20` | POS electrónico | | `93` · `94` | Nota débito · Nota crédito POS electrónico | | `25`, `27`, `30` … `60` | Otros documentos equivalentes electrónicos | Los valores `1` y `3` no son tipos de documento: una resolución creada con ellos nunca la encuentra la emisión. ::: --- ## 📋 Consulta y Sincronización **GET /resolutions — Listar Resoluciones** ```http GET {{url}}/resolutions?query=FV&active=1&limit=15&page=1&client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Retorna, paginadas, las resoluciones de la empresa, con un filtro opcional por término de búsqueda. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `query` | query | No | Coincidencia parcial en prefijo, número de resolución, nombre (`invoice_name`) o nombre del tipo de documento. También se acepta como `search`, `searchQuery` o `searchParam`. | | `active` | query | No | `1` (default) resoluciones activas, `0` resoluciones inactivas (incluidas las eliminadas con `DELETE`). | | `limit` | query | No | Registros por página. Default `15`. | | `page` | query | No | Página. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "current_page": 1, "data": [ { "id": 813, "type_document_id": 7, "resolution_number": "18764074347312", "prefix": "FV", "invoice_name": "FACTURA ELECTRÓNICA DE VENTA", "range_from": 1, "range_up": 5000000, "date_from": "2024-01-01", "date_up": "2025-12-31", "initial_number": 1, "active": 1 } ], "per_page": 15, "total": 1 }, "success": true } ``` **GET /resolutions/{id} — Obtener Resolución por ID** ```http GET {{url}}/resolutions/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID numérico de la resolución. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "data": { "id": 813, "type_document_id": 7, "prefix": "FV", "resolution_number": "18764074347312", "range_from": 1, "range_up": 5000000, "date_from": "2024-01-01", "date_up": "2025-12-31", "initial_number": 1, "active": 1 } }, "success": true } ``` **❌ Resolución no encontrada (HTTP 404)** ```json { "message": "No se encontró la resolución con ID: 813 para la empresa con ID: 1262.", "success": false } ``` **GET /numbering-range — Sincronizar Resoluciones con DIAN** ```http GET {{url}}/numbering-range?type_id=1&sync=1&client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Consulta en la DIAN los rangos de numeración autorizados para el software de la empresa y, si ese software está en **producción**, los sincroniza con las resoluciones de la empresa. Requiere un certificado digital válido. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `type_id` | query | No | Software de la empresa con el que se consulta: `1` Facturación (default), `3` Documento Soporte, `4` POS / Documento equivalente. Omitirlo equivale a `1`. La nómina electrónica no maneja rangos de numeración. | | `sync` | query | No | `1` (default) sincroniza las resoluciones; `0` solo consulta. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Cómo sincroniza:** - Solo si el software tiene `environment_id` `1` (producción) y `sync` está activo. En otro caso `sync` es `null`. - Con `type_id=1`, cada rango con `TechnicalKey` se guarda como factura electrónica (`type_document_id` `7`) y cada rango sin `TechnicalKey` como documento soporte (`11`). Con `type_id=3` se guarda como `11` y con `type_id=4` como `20`. - Cada rango se busca por tipo de documento, prefijo y número de resolución: si existe se actualiza (conserva su `initial_number`); si no, se crea con `initial_number` igual al `FromNumber` de la DIAN. En ambos casos queda activo. - Una numeración sin prefijo se sincroniza con prefijo vacío. Un rango sin `ResolutionNumber` no se guarda y se cuenta como fallido (`ResolutionNumber vacío en respuesta DIAN`). **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Consulta generada con éxito", "ResponseDian": { "Envelope": { "Body": { "GetNumberingRangeResponse": { "GetNumberingRangeResult": { "OperationCode": "100", "OperationDescription": "Acción completada OK.", "ResponseList": { "NumberRangeResponse": [ { "ResolutionNumber": "18764112492320", "ResolutionDate": "2026-07-14", "Prefix": "LZT", "FromNumber": "5000", "ToNumber": "10000", "ValidDateFrom": "2026-07-14", "ValidDateTo": "2028-07-14", "TechnicalKey": "20e961ce6420ef81a1dd2190c33234d22254cf719dd726da7fff7047c0bdda91" } ] } } } } } }, "sync": { "created": 1, "updated": 0, "failed": 0, "errors": [], "total": 1 }, "success": true } ``` | Campo de `sync` | Descripción | |---|---| | `created` · `updated` · `failed` | Resoluciones creadas, actualizadas y fallidas. | | `errors` | Detalle de los fallos: `{ "resolution_number": "...", "error": "..." }` o `{ "error": "..." }`. | | `total` | Rangos recibidos de la DIAN. | | `skipped` · `reason` | Solo aparecen cuando no se sincronizó nada: la respuesta de la DIAN no se pudo leer, no trajo rangos o su `OperationCode` no es `100`. `reason` explica el motivo. | --- ## ⚙️ Configuración Manual **POST /resolutions — Crear Resolución Manual** ```http POST {{url}}/resolutions?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Crea manualmente una resolución de facturación en la plataforma. :::info 💡 Guía para Desarrolladores — Formato del Payload `records` Este endpoint recibe los datos encapsulados dentro de la propiedad **`records`** como una cadena JSON serializada (`stringified JSON`). ::: **JavaScript / Node.js** ```javascript import axios from 'axios'; const records = { type_document_id: 7, // 7: Factura, 11: Doc Soporte, 4: Nota Débito, 5: Nota Crédito, 20: POS resolution_number: "18763005880468",// Número de resolución DIAN prefix: "FV", // Prefijo (opcional) invoice_name: "FACTURA ELECTRÓNICA DE VENTA", range_from: 1, // Rango inicial range_up: 10000, // Rango final date_from: "2026-01-01", // Vigencia desde (YYYY-MM-DD) date_up: "2028-12-31", // Vigencia hasta (YYYY-MM-DD) initial_number: 1, // Consecutivo inicial headerline1: "Encabezado", // Opcional headerline2: "Línea 2", // Opcional active: 1, // 1: Activa, 0: Inactiva technical_key: "clave_tecnica_dian"// Clave técnica DIAN (opcional) }; const response = await axios.post(`${url}/resolutions`, { records: JSON.stringify(records) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP (cURL)** ```php 7, 'resolution_number' => '18763005880468', 'prefix' => 'FV', 'invoice_name' => 'FACTURA ELECTRÓNICA DE VENTA', 'range_from' => 1, 'range_up' => 10000, 'date_from' => '2026-01-01', 'date_up' => '2028-12-31', 'initial_number' => 1, 'active' => 1, 'technical_key' => 'clave_tecnica_dian', ]; $payload = json_encode(['records' => json_encode($records)]); $ch = curl_init("{$url}/resolutions"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import json import requests records = { "type_document_id": 7, "resolution_number": "18763005880468", "prefix": "FV", "invoice_name": "FACTURA ELECTRÓNICA DE VENTA", "range_from": 1, "range_up": 10000, "date_from": "2026-01-01", "date_up": "2028-12-31", "initial_number": 1, "active": 1, "technical_key": "clave_tecnica_dian" } response = requests.post( f"{url}/resolutions", json={"records": json.dumps(records)}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" } ) ``` **C# / .NET** ```csharp using System.Text.Json; using System.Net.Http.Headers; var records = new { type_document_id = 7, resolution_number = "18763005880468", prefix = "FV", invoice_name = "FACTURA ELECTRÓNICA DE VENTA", range_from = 1, range_up = 10000, date_from = "2026-01-01", date_up = "2028-12-31", initial_number = 1, active = 1, technical_key = "clave_tecnica_dian" }; var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.PostAsJsonAsync($"{url}/resolutions", new { records = JsonSerializer.Serialize(records) }); ``` **Postman** ```javascript const records = { type_document_id: 7, resolution_number: "18763005880468", prefix: "FV", invoice_name: "FACTURA ELECTRÓNICA DE VENTA", range_from: 1, range_up: 10000, date_from: "2026-01-01", date_up: "2028-12-31", initial_number: 1, active: 1, technical_key: "clave_tecnica_dian" }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **Propiedades de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `type_document_id` | integer | ✅ Sí | Tipo de documento: `7` Factura electrónica, `11` Documento soporte, `4` Nota débito, `5` Nota crédito, `20` POS electrónico (ver la tabla de tipos al inicio de la página). | | `resolution_number` | string | ✅ Sí | Número de resolución otorgado por la DIAN. Máx. 20 caracteres. | | `prefix` | string | No | Prefijo autorizado (ej. `FV`, `NC`, `DS`). Máx. 20 caracteres. Si se omite, se guarda vacío. | | `invoice_name` | string | ✅ Sí | Título impreso del documento. Máx. 120 caracteres. | | `range_from` | integer | ✅ Sí | Número inicial del rango autorizado. | | `range_up` | integer | ✅ Sí | Número final del rango autorizado. | | `date_from` | string | ✅ Sí | Vigencia desde (`YYYY-MM-DD`). | | `date_up` | string | ✅ Sí | Vigencia hasta (`YYYY-MM-DD`). | | `initial_number` | integer | ✅ Sí | Consecutivo inicial de emisión. | | `active` | integer | ✅ Sí | `1` Activa, `0` Inactiva. | | `headerline1` · `headerline2` | string | No | Encabezados en el PDF. | | `footline1` … `footline4` | string | No | Pies de página en el PDF. | | `technical_key` | string | No | Clave técnica DIAN. | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "message": "Registro creado correctamente.", "record": { "id": 814, "company_id": 1262, "type_document_id": 7, "prefix": "FV", "invoice_name": "FACTURA ELECTRÓNICA DE VENTA", "range_from": 1, "range_up": 10000, "initial_number": 1, "date_from": "2026-01-01", "date_up": "2028-12-31", "resolution_number": "18763005880468", "technical_key": "clave_tecnica_dian", "active": 1 }, "success": true } ``` La respuesta es `200`, no `201`. `record` es la resolución creada, con su `id`. **❌ Error de Validación (HTTP 422)** Si `records` falta o no es texto JSON, la respuesta es `422` con `El campo records es requerido` o `El campo records debe ser un JSON válido`. Si faltan campos requeridos o exceden su longitud: ```json { "message": "Validación fallida (and 1 more errors)", "errors": { "invoice_name": ["El campo invoice_name es requerido"], "range_up": ["El campo range_up es requerido"] }, "success": false } ``` Con un solo error, `message` es `Validación fallida`. **❌ Resolución Duplicada (HTTP 400)** Ya existe en la empresa una resolución con el mismo tipo de documento, número y prefijo. La comprobación incluye las resoluciones inactivas, también las eliminadas con `DELETE` (el borrado es lógico). Para volver a usar una de ellas, búscala con `GET /resolutions?active=0` y reactívala con `PUT /resolutions/{id}` enviando `active` `1`. El mensaje incluye un salto de línea y espacios entre `y` y `prefijo`: ```json { "message": "Ya existe una resolución con el mismo número(18763005880468) y\n prefijo(FV). Por favor verifique", "success": false } ``` **PUT /resolutions/{id} — Actualizar Resolución** ```http PUT {{url}}/resolutions/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID numérico de la resolución a actualizar. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** `records` serializado como texto JSON, con el mismo esquema que `POST /resolutions`. :::warning Envía la resolución completa - `type_document_id`, `invoice_name`, `range_from`, `range_up`, `date_from` y `date_up` deben venir siempre; si falta alguno, la respuesta es `500` (`Server Error`). - Los demás campos que no envíes **se sobrescriben** con su valor por defecto: `prefix`, `resolution_number`, `technical_key`, encabezados y pies quedan vacíos, `initial_number` en `1` y `active` en `1`. - `type_document_id` **no se puede cambiar**: se ignora. `resolution_number` y `prefix` sí se actualizan. ::: ```json { "records": "{\"type_document_id\":7,\"resolution_number\":\"18763005880468\",\"prefix\":\"FV\",\"invoice_name\":\"FACTURA ELECTRÓNICA DE VENTA\",\"range_from\":1,\"range_up\":20000,\"date_from\":\"2026-01-01\",\"date_up\":\"2028-12-31\",\"initial_number\":1,\"active\":1,\"technical_key\":\"clave_tecnica_dian\"}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true } ``` **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` o no es texto JSON. | | `422` | `Validación fallida` | `invoice_name` o `resolution_number` vienen vacíos o exceden su longitud (120 y 20), o `prefix` excede 20 caracteres. Trae `errors` por campo y `success: false`. | | `500` | `Server Error` | Falta alguno de los campos que deben venir siempre, o el `{id}` no existe. | **DELETE /resolutions/{id} — Eliminar Resolución** ```http DELETE {{url}}/resolutions/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Borrado lógico: la resolución queda inactiva (`active` `0`) y pasa a listarse con `GET /resolutions?active=0`. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID numérico de la resolución a eliminar. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true } ``` **❌ Resolución no encontrada (HTTP 404)** ```json { "message": "No se encontró la resolución con ID: 813 para la empresa: EMPRESA EJEMPLO S.A.S.", "success": false } ``` --- # 💻 Software DIAN :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes configurar el software DIAN de tus empresas cliente agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: :::tip Valores de `environment_id` y `type_id` | Campo | Valores | |---|---| | `environment_id` | `1` Producción · `2` Pruebas / Habilitación. Al iniciar la habilitación el software pasa a `2` y, al terminarla con éxito, a `1`. | | `type_id` | `1` Facturación electrónica (facturas y notas) · `2` Nómina electrónica · `3` Documento soporte (y su nota de ajuste) · `4` POS / Documento equivalente electrónico. | Solo puede existir **un software por empresa y `type_id`**. ::: --- ## 📋 Consulta de Software DIAN **GET /software — Listar Software DIAN** ```http GET {{url}}/software?page=1&client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Lista, paginado, el software DIAN de la empresa. Cada software trae su último proceso de habilitación en `test_process` (`null` si nunca se inició). **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `page` | query | No | Página (15 por página). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "current_page": 1, "data": [ { "id": 27, "environment_id": 1, "type_id": 1, "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3", "pin": "12345", "technical_key": "fc8eac422eba16e22ffd8c6f94b3f40a6e38162c", "testsetid": "6142be76-4d36-5c08-8729-33a58c67177b", "initial_number": 100, "test_process_status": "INITIAL", "typeDescription": "Facturación", "integrationDescription": "Software propio", "test_process": null } ], "per_page": 15, "total": 1 }, "success": true } ``` **GET /software/{id} — Obtener Software DIAN por ID** ```http GET {{url}}/software/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del software registrado. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "data": { "id": 27, "environment_id": 1, "type_id": 1, "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3", "testsetid": "6142be76-4d36-5c08-8729-33a58c67177b", "initial_number": 100, "test_process": null } }, "success": true } ``` **❌ Software no encontrado (HTTP 404)** ```json { "success": false, "message": "No se encontró el software para la compañía especificada." } ``` **GET /software/test/{id} — Listado de Set de Pruebas DIAN** ```http GET {{url}}/software/test/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Lista, paginados (15 por página) y del más reciente al más antiguo, los procesos de habilitación del software indicado. Cada proceso trae su estado, el software y los documentos de prueba enviados a la DIAN. Si el software no existe o no es de la empresa, `data` viene vacío. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del software registrado (`software_id`). | | `page` | query | No | Página. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "dataRecords": { "current_page": 1, "data": [ { "id": 54, "uuid": "3b9d2f1e-5c4a-4e7b-9a10-2f6c8d1e4b73", "status": "FINISHED", "status_description": "Finalizado", "software_id": 27, "error_message": { "string": "El proceso de habilitación ha sido finalizado con éxito." }, "StatusDescription": null, "documents": [ { "id": 310, "process_id": 54, "type_document_id": 7, "document_number": "SETP990000001", "zipkey": "00000000-0000-0000-0000-000000000000", "document": { "id": 7, "code": "01", "voucher_name": "Factura electrónica" } } ], "software": { "id": 27, "type_id": 1, "environment_id": 1, "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3" } } ], "per_page": 15, "total": 1 }, "success": true } ``` | Campo | Descripción | |---|---| | `status` | `CREATED` Creado · `RUNNING` En ejecución · `SENDING` Enviado a la DIAN · `VALIDATING` Validando · `FINISHED` Finalizado · `ERROR` Error. `status_description` trae el texto. | | `error_message` · `StatusDescription` | Detalle del error devuelto por la DIAN cuando `status` es `ERROR`; al finalizar con éxito, `error_message` trae el mensaje de cierre. | | `documents` | Documentos de prueba enviados, con su tipo de documento en `document`. | --- ## ⚙️ Registro y Mantenimiento **POST /software — Registrar Software DIAN** ```http POST {{url}}/software?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Registra un nuevo software ante la plataforma para operar con la DIAN en ambiente de pruebas o producción. :::info 💡 Guía para Desarrolladores — Formato del Payload `records` Este endpoint recibe los datos encapsulados dentro de la propiedad **`records`** como una cadena JSON serializada (`stringified JSON`). ::: **JavaScript / Node.js** ```javascript import axios from 'axios'; const records = { environment_id: 2, // 1: Producción, 2: Pruebas / Habilitación (default) type_id: 1, // 1: Facturación, 2: Nómina, 3: Documento Soporte, 4: POS / Doc. equivalente testsetid: '6142be76-4d36-5c08-8729-33a58c67177b', // Set de pruebas DIAN technical_key: 'fc8eac422eba16e22ffd8c6f94b3f40a6e38162c', // Clave técnica DIAN pin: '12345', // PIN asignado en portal DIAN (obligatorio) identification: '96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3', // ID de software DIAN initial_number: 100 // Número inicial para pruebas }; const response = await axios.post(`${url}/software`, { records: JSON.stringify(records) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP (cURL)** ```php 2, 'type_id' => 1, 'testsetid' => '6142be76-4d36-5c08-8729-33a58c67177b', 'technical_key' => 'fc8eac422eba16e22ffd8c6f94b3f40a6e38162c', 'pin' => '12345', 'identification' => '96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3', 'initial_number' => 100, ]; $payload = json_encode(['records' => json_encode($records)]); $ch = curl_init("{$url}/software"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import json import requests records = { "environment_id": 2, "type_id": 1, "testsetid": "6142be76-4d36-5c08-8729-33a58c67177b", "technical_key": "fc8eac422eba16e22ffd8c6f94b3f40a6e38162c", "pin": "12345", "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3", "initial_number": 100 } response = requests.post( f"{url}/software", json={"records": json.dumps(records)}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" } ) ``` **C# / .NET** ```csharp using System.Text.Json; using System.Net.Http.Headers; var records = new { environment_id = 2, type_id = 1, testsetid = "6142be76-4d36-5c08-8729-33a58c67177b", technical_key = "fc8eac422eba16e22ffd8c6f94b3f40a6e38162c", pin = "12345", identification = "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3", initial_number = 100 }; var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.PostAsJsonAsync($"{url}/software", new { records = JsonSerializer.Serialize(records) }); ``` **Postman** ```javascript const records = { environment_id: 2, type_id: 1, testsetid: '6142be76-4d36-5c08-8729-33a58c67177b', technical_key: 'fc8eac422eba16e22ffd8c6f94b3f40a6e38162c', pin: '12345', identification: '96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3', initial_number: 100 }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **Propiedades de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `environment_id` | integer | No | Ambiente: `1` Producción, `2` Pruebas/Habilitación. Default `2`. | | `type_id` | integer | No | Tipo de software: `1` Facturación electrónica, `2` Nómina electrónica, `3` Documento soporte, `4` POS / Documento equivalente electrónico. Default `1`. Solo uno por empresa y tipo. | | `integration_type` | integer | No | `1` Software propio (default), `2` Proveedor tecnológico. | | `testsetid` | string | No | Código del TestSetId DIAN (requerido para habilitación). | | `technical_key` | string | No | Clave técnica asignada por la DIAN. Si se omite, se usa la clave de pruebas. | | `pin` | string | ✅ Sí | PIN de 5 dígitos del software en la DIAN. | | `identification` | string | ✅ Sí | ID del software generado en la DIAN. | | `initial_number` | integer | No | Consecutivo inicial de pruebas (ej. `100`). Default `1`. | El software se crea con `test_process_status` `INITIAL`. **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "message": "Registro creado correctamente.", "record": { "id": 27, "company_id": 1262, "type_id": 1, "environment_id": 2, "testsetid": "6142be76-4d36-5c08-8729-33a58c67177b", "technical_key": "fc8eac422eba16e22ffd8c6f94b3f40a6e38162c", "pin": "12345", "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3", "integration_type": 1, "initial_number": 100, "test_process_status": "INITIAL" }, "success": true } ``` La respuesta es `200`, no `201`. `record` es el software creado, con su `id`. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` o no es texto JSON. | | `400` | `Ya existe un software registrado para esta compañía con el mismo tipo.` | La empresa ya tiene un software con ese `type_id`. | | `500` | `Error interno del servidor. ...` | Falta `pin` en `records`. Incluye `reference` para soporte. | ```json { "success": false, "message": "Ya existe un software registrado para esta compañía con el mismo tipo." } ``` **PUT /software/{id} — Actualizar Software DIAN** ```http PUT {{url}}/software/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del software a actualizar. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** `records` serializado como texto JSON con los campos a cambiar. `type_id` no se puede cambiar (se ignora). Solo se guardan los valores que cambian. ```json { "records": "{\"environment_id\":1,\"testsetid\":\"\",\"technical_key\":\"fc8eac422eba16e22ffd8c6f94b3f40a6e38162c\",\"pin\":\"12345\",\"identification\":\"96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3\",\"initial_number\":100}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "message": "Software actualizado correctamente.", "dataRecords": { "id": 27, "environment_id": 1, "type_id": 1, "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3", "testsetid": "", "initial_number": 100 }, "success": true } ``` **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` o no es texto JSON. | | `404` | `No se encontró el software para la compañía especificada.` | El `{id}` no existe o no pertenece a la empresa. | **DELETE /software/{id} — Eliminar Software DIAN** ```http DELETE {{url}}/software/{id} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del software a eliminar. | :::warning El registro a eliminar se indica en `records` El body es obligatorio: `records`, serializado como texto JSON, con el `id` del software a eliminar. El software que se elimina es el que indica `records`, no el `{id}` de la ruta: envía el mismo valor en ambos. ::: ```json { "records": "{\"id\":27}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json title="Respuesta (extracto)" { "records": [ { "id": 27, "type_id": 1, "environment_id": 2, "identification": "96ab0f7c-2ab0-4de4-8c30-1268e6c8c6c3" } ], "success": true } ``` `records` lista el software eliminado; si ningún software coincide, viene vacío. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `422` | `El campo records es requerido` · `El campo records debe ser un JSON válido` | Falta `records` o no es texto JSON. | | `400` | `No se indicó qué registro eliminar.` | `records` es un objeto o arreglo vacío (`{}` o `[]`). | | `500` | `{ "error": "..." }` | Error interno. En este caso `message` es un objeto con `error` y la respuesta trae `payload`. | ```json { "success": false, "message": { "error": "..." }, "payload": "Internal Server Error" } ``` --- ## 🧪 Habilitación y Pruebas DIAN **POST /document/run-test — Iniciar Habilitación** ```http POST {{url}}/document/run-test?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Inicia automáticamente el envío de pruebas DIAN para el software indicado. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** ```json { "software_id": 27 } ``` | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `software_id` | integer | ✅ Sí | ID del software DIAN registrado con el que se iniciará la habilitación. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Se ha iniciado el proceso de habilitación de documentos electrónicos.", "process_id": 54, "success": true } ``` El proceso corre en segundo plano. Consulta su avance con `GET /software/test/{id}` usando el `id` del software. **❌ Errores** | Status | Mensaje | Causa | |:---:|---|---| | `404` | `No se encontró el software especificado.` | El `software_id` no existe o no es de la empresa. | | `500` | `Error interno del servidor. Ya existe un proceso en ejecución para este software. Por favor espere a que finalice.` | El último proceso del software no ha terminado (no está en `FINISHED` ni en `ERROR`). | | `500` | `Error interno del servidor. El software no tiene un testsetid DIAN configurado. Por favor configure uno antes de ejecutar el SET DE PRUEBAS.` | El software no tiene `testsetid`. | --- # 🔐 Certificado de Firma Digital :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: El certificado de firma digital (`.p12` o `.pfx`) es indispensable para la emisión y firma electrónica de los documentos fiscales ante la DIAN. Solo se puede tener un certificado activo por empresa. :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes administrar los certificados de tus clientes agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: :::note 🎁 Certificados incluidos en el plan Algunos planes **pagados** incluyen cupos de certificado de firma digital en la plataforma de certificados **MATICERTS**. Al activarse el plan, la empresa titular recibe automáticamente los cupos que incluye; no hay que llamar a ningún endpoint. - **Cuándo se otorgan:** en la compra, la renovación y el cambio a un plan inferior. En los planes pagados de **Casa de Software**, solo en la primera compra de un plan pagado. El cambio a un plan superior no otorga cupos nuevos, porque el cupo llegó con la compra original. - **Cupos actuales:** 1 en los planes PYME y Paquete por NIT, y 1 en los planes de Casa de Software desde 10.000 documentos. El plan FREE y el plan de Arrendamiento no incluyen cupos por ahora. - **No reciben cupos:** los planes FREE o sin cupos incluidos, las sub-cuentas (clientes) de una Casa de Software y el ambiente sandbox. ::: --- ## 🔐 Gestión del Certificado **GET /certificate — Obtener Certificado Activo** ```http GET {{url}}/certificate?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Obtiene los metadatos del certificado de firma digital actualmente registrado para la empresa emisora. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "id": 1, "name": "9001234561.p12", "description": "EMPRESA DEMO SAS", "extension": ".p12", "expiration_date": "09-07-2028 02:56:30 pm", "timestamp": "2026-07-13 23:06:11" } }, "success": true } ``` **POST /certificate — Cargar Certificado Digital** ```http POST {{url}}/certificate?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Carga un nuevo archivo `.p12` o `.pfx` codificado en **Base64** junto con su contraseña privada. Si la empresa ya tiene un certificado, usa `PUT /certificate/{id}` para reemplazarlo. :::info 💡 Guía para Desarrolladores — Envío del Certificado en Base64 El body lleva una sola propiedad, `records`, cuyo valor es un **JSON serializado como texto** con las propiedades `data`, `password` y `description`. El archivo binario `.p12` o `.pfx` debe convertirse a **Base64** y enviarse en la propiedad `data` **con el prefijo Data URI** `data:application/x-pkcs12;base64,`. La API descarta todo lo que va hasta la primera coma: sin el prefijo, el Base64 llega recortado y el certificado no se puede leer. ::: **JavaScript / Node.js** ```javascript import fs from 'fs'; import axios from 'axios'; // 1. Leer el archivo .p12 y convertir a Base64 con el prefijo Data URI const p12Buffer = fs.readFileSync('certificado.p12'); const base64Certificate = 'data:application/x-pkcs12;base64,' + p12Buffer.toString('base64'); // 2. Definir los datos del certificado const records = { data: base64Certificate, password: "password_del_certificado", description: "Certificado digital de producción" }; // 3. Enviar la petición serializada const response = await axios.post(`${url}/certificate`, { records: JSON.stringify(records) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP (cURL)** ```php $base64Cert, 'password' => 'password_del_certificado', 'description' => 'Certificado digital de producción', ]; $payload = json_encode(['records' => json_encode($records)]); $ch = curl_init("{$url}/certificate"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import base64 import json import requests with open("certificado.p12", "rb") as f: b64_cert = "data:application/x-pkcs12;base64," + base64.b64encode(f.read()).decode("utf-8") records = { "data": b64_cert, "password": "password_del_certificado", "description": "Certificado digital de producción" } response = requests.post( f"{url}/certificate", json={"records": json.dumps(records)}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" } ) ``` **C# / .NET** ```csharp using System.Text.Json; using System.Net.Http.Headers; byte[] certBytes = await File.ReadAllBytesAsync("certificado.p12"); string b64Cert = "data:application/x-pkcs12;base64," + Convert.ToBase64String(certBytes); var records = new { data = b64Cert, password = "password_del_certificado", description = "Certificado digital de producción" }; var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.PostAsJsonAsync($"{url}/certificate", new { records = JsonSerializer.Serialize(records) }); ``` **Postman** ```javascript const records = { data: "data:application/x-pkcs12;base64,MIIKgAIBAz...", password: "password_del_certificado", description: "Certificado digital de producción" }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Casa de Software). | | `records` | body | ✅ Sí | JSON serializado como texto con las propiedades de abajo. | **Propiedades de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `data` | string | ✅ Sí | Archivo `.p12` o `.pfx` en Base64, con el prefijo Data URI `data:application/x-pkcs12;base64,`. | | `password` | string | ✅ Sí | Contraseña privada del certificado. | | `description` | string | ✅ Sí | Descripción identificativa del certificado. | :::warning El nombre de la propiedad es `data` Si el archivo se envía en una propiedad con otro nombre (por ejemplo, `certificate`), la API responde `500` con el mensaje `Error interno del servidor. El certificado es requerido.`. Si falta `password` o `description`, la respuesta también es un `500`. ::: **✅ Respuesta Exitosa (HTTP 200)** El nombre del archivo se genera con el NIT y el dígito de verificación de la empresa. La contraseña y el contenido del certificado no se devuelven. ```json { "dataRecords": { "data": { "id": 1, "name": "9001234561.p12", "description": "Certificado digital de producción", "extension": ".p12", "expiration_date": "09-07-2028 02:56:30 pm" } }, "success": true } ``` **❌ Errores** | HTTP | Mensaje | Causa | |---|---|---| | `422` | `El campo records es requerido` / `El campo records debe ser un JSON válido` | Falta `records` o su valor no es un JSON válido. | | `400` | `The given data was invalid.` / `The certificate could not be read. ...` | El contenido de `data` no es Base64 válido, se envió sin el prefijo Data URI, el archivo no es un `.p12` válido o la contraseña es incorrecta. | | `500` | `Error interno del servidor. Ya existe un certificado para esta empresa.` | La empresa ya tiene un certificado. Usa `PUT /certificate/{id}`. | | `500` | `Error interno del servidor. El certificado es requerido.` | Falta la propiedad `data` en `records`. | Las respuestas `500` incluyen un campo `reference` para que soporte ubique el caso: ```json { "success": false, "message": "Error interno del servidor. Ya existe un certificado para esta empresa.", "reference": "3f1c2a9e-7b4d-4e8a-9c61-0d2b5e8f7a14" } ``` **PUT /certificate/{id} — Actualizar / Renovar Certificado** ```http PUT {{url}}/certificate/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Reemplaza el certificado digital de la empresa (por ejemplo, al renovarlo). El body tiene el mismo formato que la creación (`POST /certificate`). La ruta exige un `{id}` (puedes usar el `id` que devuelve `GET /certificate`); se actualiza siempre el certificado de la empresa autenticada o de la indicada en `client_uuid`. `PUT /certificate` sin `{id}` responde `405`. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del certificado. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | | `records` | body | ✅ Sí | JSON serializado como texto con `data` (Base64 con el prefijo Data URI), `password` y `description`. Las tres propiedades son obligatorias. | ```json { "records": "{\"data\":\"data:application/x-pkcs12;base64,MIIKgAIBAz...\",\"password\":\"password_del_certificado\",\"description\":\"Certificado digital renovado\"}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "id": 1, "name": "9001234561.p12", "description": "Certificado digital renovado", "extension": ".p12", "expiration_date": "15-08-2029 10:12:45 am", "timestamp": "2026-10-21 08:30:00" } }, "success": true } ``` **❌ Errores** | HTTP | Mensaje | Causa | |---|---|---| | `422` | `El campo records es requerido` / `El campo records debe ser un JSON válido` | Falta `records` o su valor no es un JSON válido. | | `400` | `The given data was invalid.` / `The certificate could not be read. ...` | El contenido no es Base64 válido, el archivo no es un `.p12` válido o la contraseña es incorrecta. | | `500` | `Error interno del servidor. No existe un certificado para esta empresa.` | La empresa aún no tiene certificado. Usa `POST /certificate`. | | `500` | `Error interno del servidor. El contenido del certificado es requerido.` | Falta la propiedad `data` en `records`. | | `500` | `Error interno del servidor. El certificado es requerido.` | `data` se envió sin el prefijo Data URI. | **GET /certificate/expiration/{dni} — Validar Expiración del Certificado** ```http GET {{url}}/certificate/expiration/{dni} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Verifica la fecha de expiración y vigencia del certificado digital a partir del NIT de la empresa. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `dni` | path | ✅ Sí | NIT de la empresa a consultar (sin dígito de verificación ni guiones). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": { "expiration_date": "2028-07-09 14:56:30" } }, "success": true } ``` --- # 🎨 Plantillas de Empresa (Company Templates) :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes personalizar plantillas gráficas para tus empresas cliente agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: :::caution Plan FREE La edición de plantillas (`clone-for-edit`, `customize` y `DELETE …/custom`) no está disponible en el plan FREE. En ese plan, esos endpoints responden HTTP 403: ```json { "success": false, "message": "Esta funcionalidad no está disponible en el plan FREE. Actualice su plan para acceder.", "feature": "custom_templates", "upgrade_url": "https://.../upgrade" } ``` La vista previa (`GET …/preview`) y la asignación de plantillas sí están disponibles en todos los planes. ::: :::note Identificador de la asignación En los endpoints con `{id}`, el valor es el `id` de la **asignación** (devuelto por `GET /company/templates`), no el `uuid` de la plantilla. Una asignación inexistente o de otra empresa responde HTTP 404 con `{"success": false, "message": "Recurso no encontrado."}`. ::: --- ## 📋 Catálogo y Asignación **GET /company/templates — Listar Templates Asignados** ```http GET {{url}}/company/templates?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Lista, paginadas, las plantillas gráficas asignadas a la empresa emisora, con los datos de la plantilla del catálogo en `template`. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 6, "company_id": 120, "template_id": 2, "is_default": true, "custom_config": null, "is_active": true, "custom_template_path": null, "custom_css_path": null, "template": { "id": 2, "uuid": "30f00f0a-0e92-4dfa-897f-6b6ce9fbfb98", "name": "Template Moderno", "description": "Diseño moderno a una columna", "is_active": true } } ], "per_page": 15, "total": 1 }, "success": true } ``` `custom_template_path` y `custom_css_path` tienen valor cuando la empresa guardó una personalización con `customize`. El paginador incluye además los campos estándar (`from`, `to`, `last_page`, `links`, etc.). **GET /company/templates/available — Listar Templates Disponibles** ```http GET {{url}}/company/templates/available?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Lista, paginadas, las plantillas del sistema activas que la empresa **aún no tiene asignadas**. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 3, "uuid": "4b7a1c2e-5d3f-4e8a-9b0c-1d2e3f4a5b6c", "name": "Factura Estándar 3 Columnas", "description": "Formato estándar con detalle en tres columnas", "is_active": true } ], "per_page": 15, "total": 1 }, "success": true } ``` Usa el `uuid` de la plantilla como `template_uuid` en `POST /company/templates/assign`. **POST /company/templates/assign — Asignar Template a la Empresa** ```http POST {{url}}/company/templates/assign?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** ```json { "template_uuid": "4b7a1c2e-5d3f-4e8a-9b0c-1d2e3f4a5b6c", "is_default": true } ``` | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `template_uuid` | string | ✅ Sí | UUID de una plantilla existente, obtenido de `GET /company/templates/available`. | | `is_default` | boolean | No | Si es `true`, la plantilla pasa a ser la predeterminada y las demás asignaciones dejan de serlo (default: `false`). | | `custom_config` | object | No | Configuración propia de la asignación. | **✅ Respuesta Exitosa (HTTP 201)** ```json { "message": "Template asignado exitosamente", "dataRecords": { "data": { "id": 7, "company_id": 120, "template_id": 3, "is_default": true, "custom_config": null, "is_active": true, "custom_template_path": null, "custom_css_path": null, "template": { "id": 3, "uuid": "4b7a1c2e-5d3f-4e8a-9b0c-1d2e3f4a5b6c", "name": "Factura Estándar 3 Columnas", "description": "Formato estándar con detalle en tres columnas", "is_active": true } } }, "success": true } ``` **❌ Template ya asignado (HTTP 400)** ```json { "success": false, "message": "Este template ya está asignado a la empresa" } ``` **PUT /company/templates/{id} — Actualizar Asignación** ```http PUT {{url}}/company/templates/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID de la asignación del template. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** todos los campos son opcionales; solo cambia lo que envías. ```json { "is_default": true, "custom_config": {}, "is_active": true } ``` | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `is_default` | boolean | No | Marca la asignación como predeterminada. | | `custom_config` | object | No | Configuración propia de la asignación. | | `is_active` | boolean | No | Activa o desactiva la asignación. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Configuración actualizada exitosamente", "dataRecords": { "data": { "id": 7, "company_id": 120, "template_id": 3, "is_default": true, "custom_config": {}, "is_active": true, "template": { "id": 3, "uuid": "4b7a1c2e-5d3f-4e8a-9b0c-1d2e3f4a5b6c", "name": "Factura Estándar 3 Columnas" } } }, "success": true } ``` **DELETE /company/templates/{id} — Desasignar Template** ```http DELETE {{url}}/company/templates/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID de la asignación a remover. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Template desasignado exitosamente", "success": true } ``` --- ## 🎨 Personalización y Edición **GET /company/templates/{id}/preview — Vista Previa del Template** ```http GET {{url}}/company/templates/{id}/preview?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Renderiza la versión en uso de la plantilla (la personalización de la empresa si existe, o la plantilla base) con datos de ejemplo. La vista previa usa 22 líneas de detalle para que se vea el salto de página. Disponible en todos los planes. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID de la plantilla asignada. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** Los campos van en la raíz de la respuesta: ```json { "html": "...", "css": ".invoice { color: #333; }", "css_path": "css/templates/standard.css", "template_name": "Factura Estándar", "is_custom": true, "success": true } ``` | Campo | Descripción | |---|---| | `html` | HTML renderizado con datos de ejemplo. | | `css` | Contenido del CSS de la plantilla, para inyectarlo en tu visor (puede ser `null`). | | `css_path` | Ruta relativa del CSS en uso. | | `is_custom` | `true` si se renderizó la personalización de la empresa. | **POST /company/templates/{id}/clone-for-edit — Abrir Template para Edición** ```http POST {{url}}/company/templates/{id}/clone-for-edit?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Devuelve el contenido editable de la versión que la empresa tiene en uso: su personalización (Blade y CSS) si existe, o la plantilla base si no hay personalización. No crea copias ni guarda nada; para guardar los cambios usa `customize`. No disponible en plan FREE. **✅ Respuesta Exitosa (HTTP 200)** Los campos van en la raíz de la respuesta: ```json { "blade_content": "
...
", "css_content": ".invoice { color: #333; }", "template_name": "Factura Estándar", "is_custom": false, "sample_data": {}, "success": true } ``` | Campo | Descripción | |---|---| | `blade_content` | Contenido Blade de la plantilla en uso. | | `css_content` | Contenido CSS en uso (puede ser `null`). | | `template_name` | Nombre de la plantilla del catálogo. | | `is_custom` | `true` si se abrió la personalización de la empresa; `false` si es la plantilla base. | | `sample_data` | Datos de ejemplo con los que se renderiza la plantilla, útiles para el editor. | **❌ Template fuente no encontrado (HTTP 400)** ```json { "success": false, "message": "Template fuente no encontrado" } ``` **POST /company/templates/{id}/customize — Guardar Personalización** ```http POST {{url}}/company/templates/{id}/customize?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` No disponible en plan FREE. **Body (JSON):** ```json { "blade_content": "
...
", "css_content": ".invoice { color: #0066cc; }" } ``` | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `blade_content` | string | ✅ Sí | Contenido Blade completo de la plantilla. | | `css_content` | string | No | CSS de la personalización. Si lo omites, se usa el CSS de la plantilla base. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Template personalizado guardado exitosamente", "dataRecords": { "data": { "id": 7, "company_id": 120, "template_id": 3, "is_default": true, "is_active": true, "template": { "id": 3, "uuid": "4b7a1c2e-5d3f-4e8a-9b0c-1d2e3f4a5b6c", "name": "Factura Estándar 3 Columnas" } } }, "success": true } ``` **❌ Blade con errores (HTTP 400)** ```json { "success": false, "message": "El contenido Blade contiene errores de sintaxis: ..." } ``` **DELETE /company/templates/{id}/custom — Eliminar Personalización** ```http DELETE {{url}}/company/templates/{id}/custom?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Elimina el diseño personalizado de la empresa y restaura la versión base del template. No disponible en plan FREE. **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Personalización eliminada exitosamente", "dataRecords": { "data": { "id": 7, "company_id": 120, "template_id": 3, "custom_template_path": null, "custom_css_path": null } }, "success": true } ``` --- ## 📄 Datos Adicionales en el PDF Las plantillas gráficas pueden imprimir información complementaria que **no viaja a la DIAN**: centro de costo, datos del afiliado, orden de servicio, lotes, seriales y cualquier dato que tu operación necesite ver en el documento impreso. Para ello, el JSON del documento admite tres bloques independientes: | Ámbito | Clave en el JSON | Propósito típico | |---|---|---| | Documento | [`additional_data`](https://docs.matias-api.com/docs/billing-fields#additional_data-) | Centro de costo, orden de servicio, información del afiliado | | Cliente | [`customer.extra_data`](https://docs.matias-api.com/docs/billing-fields#customer-) | Nº de socio, categoría, fecha de vinculación | | Línea de detalle | [`lines[].extra_data`](https://docs.matias-api.com/docs/billing-fields#linea-extra-data) | Lote, fecha de vencimiento, serial del producto | Ninguno de estos campos es transmitido a la DIAN. Su único efecto es sobre la **representación gráfica** (PDF) generada por la plantilla asignada a la empresa. :::caution Solo la plantilla Preprinted imprime additional_data Las plantillas **Classic**, **Minimal** y **Modern** no leen el bloque `additional_data`. La API lo acepta y lo valida igual, pero con esas plantillas no aparece en el PDF. Ver [Cómo llega al PDF](https://docs.matias-api.com/docs/billing-fields#additional-data-pdf). ::: **📐 Plantilla Preprinted — Secciones con posición fija** La plantilla **Preprinted** reconoce dos secciones de `additional_data` por su título y las coloca en un sitio fijo del formato: | El título de la sección contiene | Dónde se pinta | Etiquetas que se imprimen | |---|---|---| | `"centro de costo"` o `"centro costo"` | Recuadro **INFORMACIÓN DEL CENTRO DE COSTO**, arriba a la izquierda | El `title` de cada campo. Si envías menos de tres campos, los renglones que faltan se rotulan `Nombre del Centro de Costo`, `Dirección y Teléfono` y `Ciudad` | | `"del usuario"` o `"afiliado"` | Bloque **INFORMACIÓN DEL USUARIO**, debajo de la información del cliente | Fijas: `Afiliado Dependiente/Particular`, `Identificación` y `Descripción`. El `title` que envíes no se imprime | Reglas de estas dos secciones: - La búsqueda del título no distingue mayúsculas, acentos ni espacios: `Centro de Costo`, `CENTRO DE COSTOS`, `Centro costo` e `Información del Afiliado` coinciden. - Solo se usa la **primera** sección que coincide. Las demás secciones cuyo título también coincide no se imprimen en ninguna parte del PDF. - Se imprimen solo los **3 primeros campos**, por posición: el primero va en el primer renglón, el segundo en el segundo y el tercero en el tercero. Del 4.º en adelante no se imprimen. Envía los campos en el orden en que deben salir. - Las etiquetas del bloque del usuario son fijas; las del centro de costo salen del `title` de cada campo. - El recuadro del centro de costo forma parte del formato y aparece siempre, vacío si no envías esa sección. El bloque del usuario solo aparece cuando el documento trae sus datos. **El resto de secciones** se pinta dentro del cuerpo del documento, en el orden de las secciones: después de **INFORMACIÓN DEL CLIENTE (TERCERO)** y del bloque **INFORMACIÓN DEL USUARIO**, y **antes** de **DOCUMENTO DE REFERENCIA** (en las notas) y de **DETALLE DOCUMENTO**. Cada sección es una tabla con su título como franja (una sección sin título no lleva franja) y dos campos por renglón, con la forma `Etiqueta: valor`. **🗂️ Estructura de `additional_data`** `additional_data` es **directamente un arreglo**. Admite dos formas: **1. Arreglo de secciones** (cada una con su lista `fields`): ```json { "additional_data": [ { "section": "Centro de Costo", "order": 1, "fields": [ { "title": "Área", "value": "Ventas Norte", "type": "TEXT", "align": "LEFT", "order": 1 }, { "title": "Orden", "value": "OC-2024-001", "type": "TEXT", "align": "LEFT", "order": 2 }, { "title": "Presupuesto", "value": "5000000", "type": "CURRENCY","align": "RIGHT", "order": 3 } ] } ] } ``` **2. Lista plana de campos** (sin secciones): ```json { "additional_data": [ { "title": "Orden de servicio", "value": "OS-1001" }, { "title": "Asesor", "value": "Equipo Norte" } ] } ``` :::warning No uses el envoltorio `sections` La forma `"additional_data": { "sections": [ ... ] }` **no** es válida: la clave `sections` se interpreta como un campo suelto sin título y el documento se rechaza con HTTP 422 (`El campo sections requiere un título.`). ::: **Límites de validación:** - Máximo **10 elementos** en `additional_data`: 10 secciones o, en la lista plana, 10 campos. - Máximo **20 campos** por sección. - `section` (título): máximo 120 caracteres. - `title` del campo: obligatorio, máximo 60 caracteres. - `value` del campo: dato simple (no objeto ni arreglo), máximo 500 caracteres. **Tipos de campo permitidos:** `TEXT`, `NUMBER`, `DATE`, `CURRENCY`. :::tip Para campos tipo `CURRENCY`, envía el valor numérico en crudo (sin puntos ni comas de miles). La plantilla se encarga del formateo. ::: Para la referencia completa con ejemplos → [**Datos adicionales del documento**](https://docs.matias-api.com/docs/billing-fields#additional_data-) --- # 💰 Gestión de Monedas :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: Permite administrar el catálogo y las tasas de cambio de monedas extranjeras (ej. USD, EUR) respecto a la moneda funcional nacional (**COP** - Peso Colombiano) para emitir documentos electrónicos multi-moneda. :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes configurar las tasas de cambio de tus empresas cliente agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: --- ## 📋 Catálogo y Monedas de la Empresa **GET /currency/all — Catálogo General de Monedas** ```http GET {{url}}/currency/all Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Obtiene el catálogo completo de monedas soportadas por el sistema. **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "id": 164, "CurrencyISO": "USD", "Language": "EN", "CurrencyName": "Dólar Estadounidense", "Money": "Dólar", "Symbol": "$", "Format": "en-US", "image": "usa.png", "active": 1 }, { "id": 272, "CurrencyISO": "COP", "Language": "ES", "CurrencyName": "Peso Colombiano", "Money": "Peso", "Symbol": "$", "Format": "es-CO", "image": "colombia.png", "active": 1 } ] }, "success": true } ``` **GET /currency — Monedas Configuradas en la Empresa** ```http GET {{url}}/currency?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Obtiene, paginada (15 por página), la lista de monedas y tasas de cambio asignadas a la empresa emisora. Cada registro incluye en `currency` los datos de la moneda del catálogo. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `uid` | query | No | Filtra por el `id` de una moneda configurada en la empresa. | | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 7, "currency_id": 272, "company_id": 1, "exchange_rate_value": "0.00", "national_currency": 1, "plural_name": "PESOS", "singular_name": "PESO", "denomination": "COP", "currency": { "id": 272, "CurrencyISO": "COP", "CurrencyName": "Peso Colombiano", "Symbol": "$" } }, { "id": 8, "currency_id": 164, "company_id": 1, "exchange_rate_value": "4200.50", "national_currency": 0, "plural_name": "DÓLARES", "singular_name": "DÓLAR", "denomination": "USD", "currency": { "id": 164, "CurrencyISO": "USD", "CurrencyName": "Dólar Estadounidense", "Symbol": "$" } } ], "per_page": 15, "total": 2 }, "success": true } ``` El objeto `currency` está recortado: trae los mismos campos que `GET /currency/all`. El paginador incluye además los campos estándar (`from`, `to`, `last_page`, `links`, etc.). --- ## ⚙️ Configuración de Tasas de Cambio **POST /currency — Agregar Moneda a la Empresa** ```http POST {{url}}/currency?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Registra una nueva divisa con su tasa de cambio para la empresa emisora. :::info 💡 Guía para Desarrolladores — Configuración de Monedas Los datos de la divisa se serializan en la propiedad `records` del JSON (`Content-Type: application/json`). ::: **JavaScript / Node.js** ```javascript import axios from 'axios'; const records = { currency_id: 164, // ID del catálogo (GET /currency/all) exchange_rate_value: 4200.50,// Tasa de cambio respecto al COP national_currency: 0, // 1: Moneda funcional local, 0: Moneda extranjera plural_name: "DÓLARES", singular_name: "DÓLAR", denomination: "USD" }; const response = await axios.post(`${url}/currency`, { records: JSON.stringify(records) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP (cURL)** ```php 164, 'exchange_rate_value' => 4200.50, 'national_currency' => 0, 'plural_name' => 'DÓLARES', 'singular_name' => 'DÓLAR', 'denomination' => 'USD', ]; $payload = json_encode(['records' => json_encode($records)]); $ch = curl_init("{$url}/currency"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import json import requests records = { "currency_id": 164, "exchange_rate_value": 4200.50, "national_currency": 0, "plural_name": "DÓLARES", "singular_name": "DÓLAR", "denomination": "USD" } response = requests.post( f"{url}/currency", json={"records": json.dumps(records)}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" } ) ``` **C# / .NET** ```csharp using System.Text.Json; using System.Net.Http.Headers; var records = new { currency_id = 164, exchange_rate_value = 4200.50, national_currency = 0, plural_name = "DÓLARES", singular_name = "DÓLAR", denomination = "USD" }; var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.PostAsJsonAsync($"{url}/currency", new { records = JsonSerializer.Serialize(records) }); ``` **Postman** ```javascript const records = { currency_id: 164, exchange_rate_value: 4200.50, national_currency: 0, plural_name: "DÓLARES", singular_name: "DÓLAR", denomination: "USD" }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **Propiedades de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `currency_id` | integer | ✅ Sí | ID de la moneda del catálogo (`GET /currency/all`). | | `exchange_rate_value` | number | No | Tasa de cambio respecto al peso colombiano (default: `0`). | | `national_currency` | integer | No | `1` si es la moneda funcional local, `0` si es extranjera (default: `0`). | | `plural_name` | string | No | Nombre en plural (ej. `DÓLARES`). | | `singular_name` | string | No | Nombre en singular (ej. `DÓLAR`). | | `denomination` | string | No | Código ISO de la moneda (ej. `USD`, `EUR`). | El body debe incluir `records` como **string JSON**. Esto aplica a `POST`, `PUT` y `DELETE` de `/currency`. **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Registro creado correctamente.", "record": { "id": 8, "currency_id": 164, "company_id": 1, "exchange_rate_value": "4200.50", "national_currency": 0, "plural_name": "DÓLARES", "singular_name": "DÓLAR", "denomination": "USD" }, "success": true } ``` **❌ Moneda duplicada (HTTP 400)** La moneda ya está configurada para la empresa: ```json { "success": false, "message": "La moneda ya existe para la empresa" } ``` **❌ Sin `records` o JSON inválido (HTTP 422)** ```json { "message": "El campo records es requerido", "errors": { "records": ["El campo records es requerido"] } } ``` Si `records` no es un JSON válido, el mensaje es `El campo records debe ser un JSON válido`. **PUT /currency/{id} — Actualizar Tasa de Moneda** ```http PUT {{url}}/currency/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID de la moneda configurada en la empresa. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** `records` como string JSON, con las mismas propiedades que en `POST`. `currency_id` es obligatorio; `exchange_rate_value` y `national_currency` toman `0` si los omites, y los nombres quedan vacíos. Envía siempre la configuración completa de la moneda. ```json { "records": "{\"currency_id\":164,\"exchange_rate_value\":4350.25,\"national_currency\":0,\"plural_name\":\"DÓLARES\",\"singular_name\":\"DÓLAR\",\"denomination\":\"USD\"}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true } ``` **DELETE /currency/{id} — Eliminar Moneda** ```http DELETE {{url}}/currency/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID de la moneda configurada a eliminar. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** `records` es obligatorio también en el borrado. Cada campo que envíes dentro de `records` se añade como condición: solo se elimina la fila que coincida con el `id` **y** con esos valores. Para borrar solo por `id`, envía un objeto vacío. ```json { "records": "{}" } ``` **✅ Respuesta Exitosa (HTTP 200)** Devuelve las filas eliminadas (un arreglo vacío si ninguna coincidió): ```json { "records": [ { "id": 8, "currency_id": 164, "company_id": 1, "exchange_rate_value": "4200.50", "national_currency": 0, "plural_name": "DÓLARES", "singular_name": "DÓLAR", "denomination": "USD" } ], "success": true } ``` --- ## 💱 Tasa de Cambio **GET /currency/change — Cotizar entre dos Monedas** ```http GET {{url}}/currency/change?source=USD&target=COP&quantity=100 Authorization: Bearer {token} ``` **Descripción:** Consulta la cotización entre dos monedas con un proveedor externo de tasas de cambio. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `source` | query | ✅ Sí | Código ISO de la moneda de origen (ej. `USD`). | | `target` | query | ✅ Sí | Código ISO de la moneda de destino (ej. `COP`). | | `quantity` | query | ✅ Sí | Cantidad a convertir. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "value": 4200.5, "amount": 420050, "success": true } ``` **GET /currency/change/local — Convertir a la Moneda Nacional** ```http GET {{url}}/currency/change/local?source=USD&quantity=100 Authorization: Bearer {token} ``` **Descripción:** Convierte una cantidad desde la moneda `source` hacia la moneda marcada como nacional (`national_currency: 1`). Requiere tener configurada la moneda nacional. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `source` | query | ✅ Sí | Código ISO de la moneda de origen (ej. `USD`). | | `quantity` | query | No | Cantidad a convertir (default: `1`). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "records": [ { "value": 4200.5, "amount": 420050 } ], "success": true } ``` **❌ Moneda nacional no configurada (HTTP 404)** ```json { "success": false, "message": "Debe crear la moneda nacional." } ``` Si la moneda nacional configurada no existe en el catálogo, el mensaje es `No existe la moneda.` :::note Sandbox En sandbox, ambos endpoints de tasa de cambio devuelven un factor fijo de `1.0`: `value` y `amount` valen `1.0 × quantity`. ::: --- # 📊 Configuración de Reportes :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes configurar encabezados y logos de reporte para tus clientes agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: --- ## 📋 Encabezados y Pies de Página **GET /settings/reports — Listar Encabezados de Reporte** ```http GET {{url}}/settings/reports?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Lista, paginados (15 por página), los encabezados de reporte de la empresa. Si la empresa aún no tiene ninguno, el servidor crea uno vacío y lo devuelve, así que la lista nunca llega vacía. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | query | No | Filtra por el `id` de un encabezado. | | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 7, "company_id": 120, "line1": "

EMPRESA S.A.S

N.I.T: 999.999.999-2

", "line2": "

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

", "foot": "

Email: contacto@empresa-ejemplo.com

", "image": null } ], "per_page": 15, "total": 1 }, "success": true } ``` El paginador incluye además los campos estándar (`from`, `to`, `last_page`, `links`, etc.). **POST /crud?tbPrefix=T063 — Crear Encabezado de Reporte** ```http POST {{url}}/crud?tbPrefix=T063&client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Crea un nuevo encabezado y pie de página para los reportes y representaciones gráficas enviando el contenido formateado en HTML dentro del objeto `records` serializado en JSON. :::info 💡 Guía para Desarrolladores — Formato HTML en `records` Puedes enviar contenido con etiquetas HTML (negrita, alineación, saltos de línea) dentro de `line1`, `line2` y `foot`. ::: **JavaScript / Node.js** ```javascript import axios from 'axios'; const records = { line1: '

EMPRESA S.A.S

N.I.T: 999.999.999-2

IVA REGIMEN COMÚN

', line2: '

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

', foot: '

Email: contacto@empresa-ejemplo.com - Sitio web: https://empresa-ejemplo.com

' }; const response = await axios.post(`${url}/crud?tbPrefix=T063`, { records: JSON.stringify(records) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP (cURL)** ```php '

EMPRESA S.A.S

N.I.T: 999.999.999-2

', 'line2' => '

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

', 'foot' => '

Email: contacto@empresa-ejemplo.com

', ]; $payload = json_encode(['records' => json_encode($records)]); $ch = curl_init("{$url}/crud?tbPrefix=T063"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import json import requests records = { "line1": '

EMPRESA S.A.S

N.I.T: 999.999.999-2

', "line2": '

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

', "foot": '

Email: contacto@empresa-ejemplo.com

' } response = requests.post( f"{url}/crud?tbPrefix=T063", json={"records": json.dumps(records)}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" } ) ``` **C# / .NET** ```csharp using System.Text.Json; using System.Net.Http.Headers; var records = new { line1 = "

EMPRESA S.A.S

N.I.T: 999.999.999-2

", line2 = "

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

", foot = "

Email: contacto@empresa-ejemplo.com

" }; var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.PostAsJsonAsync($"{url}/crud?tbPrefix=T063", new { records = JsonSerializer.Serialize(records) }); ``` **Postman** ```javascript const records = { line1: '

EMPRESA S.A.S

N.I.T: 999.999.999-2

', line2: '

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

', foot: '

Email: contacto@empresa-ejemplo.com

' }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **Propiedades de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `line1` | string (HTML) | ✅ Sí | Línea principal del encabezado (Razón social, NIT, régimen). | | `line2` | string (HTML) | No | Línea secundaria (Dirección, ciudad, teléfonos). | | `foot` | string (HTML) | No | Pie de página del documento (Canales de contacto, web, notas). | **✅ Respuesta Exitosa (HTTP 200)** Devuelve la fila creada en `payload`. Si envías `records` como arreglo de objetos, `payload` trae la lista de filas creadas. ```json { "message": "Registro creado correctamente.", "payload": { "id": 7, "company_id": 120, "line1": "

EMPRESA S.A.S

N.I.T: 999.999.999-2

", "line2": "

Calle 00 # 00-00. Ciudad Ejemplo - Departamento CEL: 300-000-0000

", "foot": "

Email: contacto@empresa-ejemplo.com

", "image": null }, "success": true } ``` **📏 Reglas del CRUD genérico (`/crud`)** - `company_id` lo fija siempre el servidor con la empresa del token (o del `client_uuid`) y no se puede cambiar. `created_at` y `deleted_at` se ignoran. Si la tabla exige `uuid`, el servidor lo genera, y no se puede cambiar en un `PUT`. - `records` debe ser un objeto JSON o un arreglo de objetos. Con un arreglo, cada registro se valida por separado y la respuesta 400 trae el primer error. En un `PUT` con arreglo, cada registro debe incluir su llave primaria. - Solo se pueden escribir tablas propias de la empresa. Los catálogos y la configuración del sistema son de solo lectura. - Un registro de otra empresa responde igual que uno inexistente (404). - Un filtro `where` con condiciones `OR` no amplía el alcance: siempre se filtra por la empresa. - Las personas de recepción (`tbPrefix=T005`) no se escriben por el CRUD: se gestionan con `/events/reception-people`. Además, el módulo de recepción exige un plan de pago, también para leerlas: sin él, la respuesta es 403 con `error_code: RECEPTION_NOT_IN_PLAN`. | HTTP | Mensaje | Causa | |---|---|---| | 400 | `No se ha especificado el prefijo de la tabla (tbPrefix).` | Falta `tbPrefix`. | | 400 | `Debe enviar los registros (records).` | Falta `records` o viene vacío. | | 400 | `Cada registro de records debe ser un objeto JSON.` | Un elemento de `records` no es un objeto. | | 400 | `Cada registro debe incluir su id.` | `PUT` con arreglo sin la llave primaria en algún registro. | | 403 | `La tabla '' es de solo lectura: pertenece a la configuración del sistema y no se puede modificar por el CRUD.` | Escritura sobre un catálogo del sistema. | | 403 | `La tabla '' es de solo lectura por el CRUD: se gestiona con /api/ubl2.1/events/reception-people.` | Escritura sobre `T005`. | | 404 | `El prefijo de tabla '' no existe.` | `tbPrefix` desconocido. | | 404 | `La tabla '' no está disponible en este sistema.` | El prefijo existe pero la tabla no está disponible. | | 404 | `Recurso no encontrado.` | El registro no existe o es de otra empresa. | **PUT /settings/reports/{id} — Actualizar Encabezado** ```http PUT {{url}}/settings/reports/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del encabezado a actualizar. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** `records` como string JSON con los campos a actualizar (`line1`, `line2`, `foot`). Si incluyes `imgdata` para cambiar también el logo, debes enviar `imgname` (ver [Actualizar Logo del Encabezado](https://docs.matias-api.com/docs/endpoints/report-settings#logotipo)). ```json { "records": "{\"line1\":\"

EMPRESA S.A.S

\",\"foot\":\"

Gracias por su compra

\"}" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true } ``` **❌ Registro No Encontrado (HTTP 404)** El encabezado no existe o no pertenece a la empresa. Si falta `records` o no es un JSON válido, la respuesta es HTTP 422 (`El campo records es requerido` o `El campo records debe ser un JSON válido`). ```json { "success": false, "message": "No se encontró el registro con id 7 para la compañía: EMPRESA DE EJEMPLO SAS" } ``` **DELETE /settings/reports/{id} — Eliminar Encabezado** ```http DELETE {{url}}/settings/reports/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID del encabezado a eliminar. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** La respuesta no incluye el campo `success`: ```json { "message": "Configuración eliminada exitosamente" } ``` **❌ Registro No Encontrado (HTTP 404)** ```json { "success": false, "message": "No se encontró el registro con id 7 para la compañía: EMPRESA DE EJEMPLO SAS" } ``` --- ## 🖼️ Logotipo de Reportes **PUT /settings/reports/{id}/image — Actualizar Logo del Encabezado** ```http PUT {{url}}/settings/reports/{id}/image?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Descripción:** Actualiza el logo asociado a un encabezado de reporte enviando la imagen codificada en **Base64** dentro del objeto `records`. :::info 💡 Guía para Desarrolladores — Carga de Imagen en Base64 Convierte la imagen (PNG/JPG) a Base64 con el prefijo Data URI (ej. `data:image/png;base64,...`) y envíala en `imgdata`, junto con `imgname`, el nombre del archivo original (ej. `logo.png`). ::: **Propiedades de `records`:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `imgdata` | string | ✅ Sí | Imagen como Data URI en Base64. Se toma lo que sigue a la primera coma. | | `imgname` | string | ✅ Sí | Nombre del archivo original. Solo se usa su extensión: `png`, `jpg`, `jpeg`, `gif`, `webp`, `svg`, `jfif`, `bmp` o `ico`; cualquier otra se guarda como `png`. El nombre final del archivo lo asigna el servidor. | **JavaScript / Node.js** ```javascript import fs from 'fs'; import axios from 'axios'; const imgBuffer = fs.readFileSync('logo.png'); const base64Img = 'data:image/png;base64,' + imgBuffer.toString('base64'); const response = await axios.put(`${url}/settings/reports/${id}/image`, { records: JSON.stringify({ imgdata: base64Img, imgname: 'logo.png' }) }, { headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } }); ``` **PHP** ```php json_encode([ 'imgdata' => $base64Img, 'imgname' => 'logo.png', ])]); $ch = curl_init("{$url}/settings/reports/{$id}/image"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $token, 'Content-Type: application/json' ], ]); $response = curl_exec($ch); curl_close($ch); ``` **Python** ```python import base64 import json import requests with open("logo.png", "rb") as f: b64_img = "data:image/png;base64," + base64.b64encode(f.read()).decode("utf-8") response = requests.put( f"{url}/settings/reports/{id}/image", json={"records": json.dumps({"imgdata": b64_img, "imgname": "logo.png"})}, headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" } ) ``` **Postman** ```javascript const records = { imgdata: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", imgname: "logo.png" }; const payload = { records: JSON.stringify(records) }; pm.request.body.mode = 'raw'; pm.request.body.raw = JSON.stringify(payload); pm.request.headers.add({key: 'Content-Type', value: 'application/json'}); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "success": true } ``` **❌ Errores** | HTTP | Mensaje | Causa | |---|---|---| | 422 | `El campo records es requerido` | Falta `records` (formato estándar de validación, con `errors`). | | 422 | `El campo records debe ser un JSON válido` | `records` no es un JSON válido. | | 404 | `No se encontró el registro con id para la compañía: ` | El encabezado no existe o no pertenece a la empresa. | | 404 | `La propiedad IMGDATA no está definida.` | `records` no incluye `imgdata`. | | 404 | `La cadena base64 de la imagen no es válida.` | `imgdata` viene vacío. | | 500 | `Error interno del servidor. La propiedad RECORDS no está definida.` | `records` es un JSON vacío o nulo (por ejemplo `null` o `[]`). | | 500 | `Error interno del servidor. ...` | `records` no incluye `imgname`. Envíalo siempre junto con `imgdata`. | | 500 | Respuesta `{"success": false, "error": "..."}` | Falló la actualización del registro. | ```json { "success": false, "message": "No se encontró el registro con id 7 para la compañía: EMPRESA DE EJEMPLO SAS" } ``` Las respuestas 500 incluyen además un `reference` para que soporte ubique el error. --- # 📧 Registros de Email :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes auditar y consultar los registros de correos de tus empresas cliente agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: Permite auditar el estado de entrega, rebotes y aperturas de los correos electrónicos enviados a los adquirentes con las representaciones gráficas (PDF) y archivos XML adjuntos. --- ## 📋 Consulta de Logs de Envío Los tres endpoints devuelven solo los correos de la empresa del token (o la indicada con `client_uuid`). **📑 Campos de un registro de email** | Campo | Descripción | |---|---| | `id` | ID del registro de email. | | `company_id`, `company_name` | Empresa emisora. | | `customer_id`, `person_name` | Adquirente al que se envió el correo. | | `document_id` | ID interno del documento electrónico. | | `type_document_id`, `voucher_name` | Tipo de documento (ID y nombre). | | `message_id` | Identificador del mensaje en el proveedor de correo. | | `email` | Dirección de destino. | | `status` | `sent`, `delivered`, `bounced` o `complained`. Otros valores se devuelven tal cual. | | `status_translated` | `Enviado`, `Entregado`, `Rebotado` o `Reclamado`. | | `opens`, `clicks` | Número de aperturas y de clics. | | `last_opened_at`, `last_clicked_at` | Última apertura y último clic. | | `delivered_at`, `bounced_at`, `complained_at` | Fecha de entrega, de rebote y de queja. | | `bounce_type`, `bounce_subtype` | Tipo y subtipo de rebote, si lo hubo. | | `created_at`, `updated_at` | Fecha de creación y de última actualización del registro. | Las fechas sin sufijo vienen formateadas como `dd/mm/YYYY hh:mm:ss AM/PM`. Cada una tiene una variante `*_original` (por ejemplo `delivered_at_original`) con el valor sin formato (`YYYY-MM-DD HH:MM:SS`), útil para ordenar o comparar. **GET /email-logs — Listar Registros de Email** ```http GET {{url}}/email-logs?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Lista paginada de 20 registros por página, sin un orden garantizado. Usa `?page=` para navegar. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | :::note El tamaño de página es fijo (20). Un parámetro `limit` en este endpoint se ignora. ::: **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 104, "company_id": 120, "company_name": "EMPRESA DE EJEMPLO SAS", "customer_id": 55, "person_name": "CLIENTE DE EJEMPLO SAS", "document_id": 836, "type_document_id": 7, "voucher_name": "Factura electrónica de venta", "message_id": "0100018d-ejemplo-000000", "email": "cliente@ejemplo.com", "status": "delivered", "status_translated": "Entregado", "opens": 1, "last_opened_at": "20/08/2026 11:02:10 AM", "last_opened_at_original": "2026-08-20 11:02:10", "clicks": 0, "last_clicked_at": null, "last_clicked_at_original": null, "delivered_at": "20/08/2026 10:15:35 AM", "delivered_at_original": "2026-08-20 10:15:35", "bounced_at": null, "bounced_at_original": null, "complained_at": null, "complained_at_original": null, "bounce_type": null, "bounce_subtype": null, "created_at": "20/08/2026 10:15:30 AM", "created_at_original": "2026-08-20 10:15:30", "updated_at": "20/08/2026 11:02:10 AM", "updated_at_original": "2026-08-20 11:02:10" } ], "per_page": 20, "total": 1 }, "success": true } ``` El paginador incluye además los campos estándar (`from`, `to`, `last_page`, `links`, `next_page_url`, etc.). **GET /email-logs/{id} — Obtener Registro por ID** ```http GET {{url}}/email-logs/{id}?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `id` | path | ✅ Sí | ID numérico del registro de email. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | :::danger No disponible temporalmente Hoy este endpoint responde HTTP 500 en todos los casos. Mientras se corrige, consulta los registros con `GET /email-logs` o con `GET /email-logs/document/{document_id}`. ::: **GET /email-logs/document/{document_id} — Buscar por ID de Documento** ```http GET {{url}}/email-logs/document/{document_id}?type_document_id={type_document_id}&client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json ``` Devuelve los correos enviados para un documento, del más reciente al más antiguo. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `document_id` | path | ✅ Sí | ID interno del documento electrónico emitido (el `id` del documento, no su número). | | `type_document_id` | query | ✅ Sí | ID del tipo de documento. Sin él, la consulta no devuelve los correos del documento. | | `limit` | query | No | Registros por página (default: `15`). | | `page` | query | No | Número de página (default: `1`). | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "current_page": 1, "data": [ { "id": 104, "document_id": 836, "type_document_id": 7, "voucher_name": "Factura electrónica de venta", "email": "cliente@ejemplo.com", "status": "delivered", "status_translated": "Entregado", "opens": 1, "clicks": 0, "delivered_at": "20/08/2026 10:15:35 AM", "delivered_at_original": "2026-08-20 10:15:35", "created_at": "20/08/2026 10:15:30 AM", "created_at_original": "2026-08-20 10:15:30" } ], "per_page": 15, "total": 1 }, "success": true } ``` El ejemplo está recortado: cada elemento trae todos los [campos de un registro de email](https://docs.matias-api.com/docs/endpoints/email-logs#consulta-logs). --- ## 🔔 Avisos de Correos No Entregados Cuando un correo no se puede entregar, la plataforma avisa **solo a la cuenta maestra**, nunca al adquirente ni a la sub-cuenta: - **Sub-cuenta** de una Casa de Software: los avisos llegan a la **Casa de Software**. - **Casa de Software o cuenta directa:** recibe sus propios avisos. Los avisos cubren el documento no enviado, el envío fallido, el destinatario suprimido, el rebote o la queja del destinatario y el evento de recepción rechazado por la DIAN. **Destino:** la configuración `NOTIFICATIONEMAIL` de la cuenta maestra (ver `GET /company/settings`). Admite varias direcciones separadas por `;` o `,`. Si está vacía o no es válida, se usa el email de la empresa. **Resumen diario:** los rebotes, las quejas y los envíos bloqueados no generan un aviso por cada incidente. Se consolidan en **un único correo diario a las 07:00 (hora de Colombia)** por cuenta maestra, agrupado por emisor, dirección y motivo, con un archivo CSV de detalle. El resumen diario no se envía desde el ambiente sandbox. :::tip Cómo detectarlo en tu integración No dependas del correo de aviso. Cuando el envío del correo falla al emitir un documento, la respuesta de la emisión trae el warning `EMAIL_NOT_SENT` (ver [Facturas y POS](https://docs.matias-api.com/docs/endpoints/invoices-pos.md)). Para seguir la entrega después, consulta estos registros o suscríbete a los eventos `email.*` de [Webhooks](https://docs.matias-api.com/docs/endpoints/webhooks.md). ::: --- # 📦 Envío Masivo (Bulk) — Referencia de Endpoints :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}`. Envía también `Accept: application/json` para recibir todos los errores en JSON. ::: :::tip Guía completa Esta página es la referencia rápida de endpoints. Estados, catálogo de errores, idempotencia, modos de envío, webhook y retención se explican en la guía [Envío Masivo de Documentos (Bulk)](https://docs.matias-api.com/docs/bulk-documents.md). Si ya integrabas el envío masivo, revisa [Cambios y compatibilidad](https://docs.matias-api.com/docs/bulk-documents#cambios-y-compatibilidad) 🆕. ::: :::danger ⚠️ El lote se elimina a los 7 días Un lote terminado y sus items se **eliminan por completo a los 7 días de la creación del lote**. Guarda el `document_uuid` de cada item mientras el lote se procesa o apenas termine. Ver [Retención de datos](https://docs.matias-api.com/docs/bulk-documents#retencion). ::: :::warning Membresía requerida también para consultar Todo el grupo `/bulk` exige una membresía con cupo: si la empresa agotó su cuota o su membresía está inactiva, también los `GET` responden `402` (`Límite de documentos de la membresía alcanzado o membresía inactiva.`). ::: :::info Parámetro Multi-Tenant: `client_uuid` Si operas como **Casa de Software**, puedes emitir y consultar lotes en nombre de una empresa cliente agregando `?client_uuid={{client_uuid}}`. ```http GET {{url}}/company/customers Authorization: Bearer {token} ``` ::: --- ## 🚀 Emisión y Procesamiento de Lotes **POST /bulk/documents — Crear Lote de Documentos (Asíncrono)** ```http POST {{url}}/bulk/documents?client_uuid={{client_uuid}} Authorization: Bearer {token} Content-Type: application/json Accept: application/json Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d ``` **Descripción:** Acepta un lote de documentos electrónicos para procesamiento en cola. Retorna `201 Created` inmediatamente con `batch_id` para consultar su progreso. **Headers y Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `Idempotency-Key` | header | No | UUID v4 (máx. 36 caracteres) para no crear dos veces el mismo lote. Válido por 24 h. Mismo key + mismo body: `201` con el lote original; body distinto: `409`. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `mode` | string | ✅ Sí | `auto-increment` o `manual`. | | `stop_on_error` | boolean | No | Si `true`, el primer documento que no termine en `SUCCESS` cancela los items en cola. Default `false`. | | `default_resolution_number` | string | No | Máx. 50. Se copia en `payload.resolution_number` si el payload no lo trae. | | `default_prefix` | string | No | Máx. 10. Se copia en `payload.prefix` si el payload no lo trae. | | `documents` | array | ✅ Sí | Mínimo 1. Máximo según plan (FREE 5, Pago 50, Ilimitado 200); nunca más de 200. | | `documents[].kind` | string | ✅ Sí | `invoice`, `credit_note`, `debit_note`, `support_document`, `adjustment_note` o `pos_document`. | | `documents[].payload` | object | ✅ Sí | Body del endpoint unitario equivalente. | | `documents[].uuid` | UUID | No | UUID anti-duplicados, distinto dentro del lote. | | `documents[].client_reference` | string | No | Tu referencia interna (máx. 64). | | `documents[].attachments` | array | No | Máx. 4 adjuntos: `filename` (requerido, máx. 255), `content` (requerido, base64), `content_type` (opcional, máx. 100). Se ignoran si `payload.attachments` ya trae adjuntos. | Cada documento va envuelto en `kind` + `payload`: los campos del documento (`type_document_id`, `customer`, `lines`…) van **dentro** de `payload`. El ejemplo usa un payload abreviado; envía el JSON completo del endpoint unitario. ```json { "mode": "auto-increment", "stop_on_error": false, "default_resolution_number": "18764002566734", "default_prefix": "SETT", "documents": [ { "kind": "invoice", "uuid": "6f1c2b9e-3d4a-4e8b-9c71-2a5d8e0f4b13", "client_reference": "FACT-001", "payload": { "type_document_id": 7, "operation_type_id": 1, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "595000.00" } ], "customer": { "dni": "900123456", "company_name": "CLIENTE A SAS", "email": "facturacion@ejemplo.com" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "500000.00", "free_of_charge_indicator": false, "description": "Servicio de Consultoría", "code": "SRV-001", "price_amount": "500000.00", "base_quantity": "1" } ], "legal_monetary_totals": { "line_extension_amount": "500000.00", "tax_exclusive_amount": "500000.00", "tax_inclusive_amount": "595000.00", "payable_amount": "595000.00" } } } ] } ``` **✅ Respuesta Exitosa (HTTP 201 Created)** ```json { "message": "Lote aceptado para procesamiento asíncrono.", "batch_id": "b8f4155a-7ca2-11f0-be83-d843ae899220", "status": "PENDING", "status_url": "/api/ubl2.1/bulk/documents/b8f4155a-7ca2-11f0-be83-d843ae899220", "received": 1, "accepted": 1, "rejected": 0, "estimated_completion_seconds": 6, "items": [ { "index": 0, "client_reference": "FACT-001", "item_id": "c1d2e3f4-a1b2-c3d4-e5f6-a7b8c9d0e1f2", "status": "QUEUED" } ], "success": true } ``` **❌ Errores (HTTP 402 / 409 / 413 / 422 / 429)** | HTTP | Causa | Cuerpo | |---|---|---| | `402` | Membresía sin cupo o inactiva | `success: false` + `message` | | `402` | Cuota insuficiente para el lote completo | `success: false` + `message` + `requested` (o solo `message` al revalidar antes de crear el lote) | | `409` | `Idempotency-Key` reutilizado con un body distinto | Solo `message` | | `413` | El lote supera el tope de tu plan (se valida antes que el esquema) | `success: false` + `message` + `max_items` + `received` + `upgrade_url` | | `422` | Esquema inválido | Formato estándar de validación: `message` + `errors` (sin `success`) | | `422` | `documents` vacío o ausente (se valida antes que el esquema) | `success: false` + `message` (`Compañía o lote inválido.`) | | `422` | `Idempotency-Key` de más de 36 caracteres | Solo `message` | | `429` | Lotes por minuto excedidos (por usuario y por empresa), demasiados documentos en proceso (máx. 1000) u otro lote de la empresa en admisión | `message` y header `Retry-After` (en el plan FREE: `success: false` + `message` + `upgrade_url`, sin `Retry-After`: espera 60 segundos) | Orden de los controles: lotes por minuto (`429`) → membresía (`402`) → `documents` vacío (`422`), tope del plan (`413`) y cuota (`402`) → esquema (`422`) → `Idempotency-Key` (`422`) → lote en admisión (`429`) → replay o `409` → documentos en proceso (`429`) → cuota revalidada (`402`). Un reintento con el mismo `Idempotency-Key` también pasa por todos los controles previos al replay. Ver [Orden de los controles](https://docs.matias-api.com/docs/bulk-documents#orden-controles). ```json { "message": "Cada documento debe especificar su tipo (kind). (and 1 more error)", "errors": { "documents.0.kind": ["Cada documento debe especificar su tipo (kind)."], "documents.0.payload": ["Cada documento debe incluir su payload."] } } ``` Ejemplos y mensajes exactos de cada caso: [Códigos de Error HTTP](https://docs.matias-api.com/docs/bulk-documents#errores). --- ## 📊 Consulta de Estado del Lote **GET /bulk/documents/{batchId} — Consultar Estado del Lote** ```http GET {{url}}/bulk/documents/{batchId}?client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `batchId` | path | ✅ Sí | UUID del lote obtenido al crearlo. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | Retorna el lote con todos sus items ordenados por `index`. Estados del lote: `PENDING`, `PROCESSING`, `COMPLETED`, `PARTIAL`, `FAILED`. Contadores: `succeeded` (items `SUCCESS`) y `failed` (items `FAILED`, `REJECTED_BY_DIAN` y `POSSIBLY_SENT`). **✅ Respuesta Exitosa (HTTP 200)** ```json { "uuid": "b8f4155a-7ca2-11f0-be83-d843ae899220", "company_id": 1, "user_id": 5, "mode": "auto-increment", "stop_on_error": false, "total_items": 1, "succeeded": 1, "failed": 0, "status": "COMPLETED", "idempotency_key": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "finished_at": "2026-08-20T14:30:12.000000Z", "created_at": "2026-08-20T14:30:00.000000Z", "updated_at": "2026-08-20T14:30:12.000000Z", "batch_id": "b8f4155a-7ca2-11f0-be83-d843ae899220", "status_description": "Completado", "received": 1, "items": [ { "uuid": "c1d2e3f4-a1b2-c3d4-e5f6-a7b8c9d0e1f2", "index": 0, "kind": "invoice", "client_reference": "FACT-001", "document_uuid": "3e7a9c51-8b2d-4f6e-a1c4-9d0b2e5f7a38", "shipping_history_id": 4200027, "status": "SUCCESS", "attempts": 1, "error_code": null, "error_message": null, "created_at": "2026-08-20T14:30:00.000000Z", "updated_at": "2026-08-20T14:30:12.000000Z", "processed_at": "2026-08-20T14:30:12.000000Z", "response": "{\"uuid\":\"3e7a9c51-8b2d-4f6e-a1c4-9d0b2e5f7a38\",\"document_number\":\"SETT101\",\"XmlDocumentKey\":\"cf9864294501e8a9578235dd2ab3c4fd\",\"cufe\":\"cf9864294501e8a9578235dd2ab3c4fd\",\"track_id\":null,\"is_valid\":true,\"status_code\":200,\"dian_status_code\":\"00\",\"dian_status\":\"ACCEPTED\",\"errors\":[],\"notifications\":[],\"warnings\":[],\"http_status\":200}", "item_id": "c1d2e3f4-a1b2-c3d4-e5f6-a7b8c9d0e1f2", "kind_description": "Factura electrónica", "status_description": "Exitoso", "document_number": "SETT101", "cufe": "cf9864294501e8a9578235dd2ab3c4fd", "track_id": null, "dian_response": { "is_valid": true, "status_code": "00", "status": "ACCEPTED", "errors": [], "notifications": [] }, "warnings": [] } ], "success": true } ``` Guarda el `document_uuid` de cada item: es el UUID del envío del documento (el que usas en `GET /documents/{uuid}`) y, en general, no coincide con el `documents[].uuid` que enviaste. Los campos de cada item cambian según su estado (`error` en `FAILED` y `REJECTED_BY_DIAN`; `error` y `next_action: "CHECK_STATUS"` en `POSSIBLY_SENT`). Ver [Campos de cada item](https://docs.matias-api.com/docs/bulk-documents#campos-item). **❌ Lote no encontrado (HTTP 404)** Si el lote no existe, ya se purgó o pertenece a otra empresa, la respuesta trae un único campo `message` (sin `success`). Sin `Accept: application/json` puede llegar como página HTML. **GET /bulk/documents/{batchId}/items — Listar Items del Lote** ```http GET {{url}}/bulk/documents/{batchId}/items?page=1&per_page=50&status=FAILED&client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `batchId` | path | ✅ Sí | UUID del lote. | | `status` | query | No | Filtrar por estado: `QUEUED`, `PROCESSING`, `SUCCESS`, `FAILED`, `REJECTED_BY_DIAN`, `POSSIBLY_SENT`, `CANCELLED`. | | `kind` | query | No | Filtrar por tipo: `invoice`, `credit_note`, `debit_note`, `support_document`, `adjustment_note`, `pos_document`. | | `per_page` | query | No | Registros por página (default `50`, de 1 a 200). | | `page` | query | No | Número de página. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** Paginación estándar; cada elemento de `data` tiene los mismos campos que `items[]` del lote. ```json { "current_page": 1, "data": [ { "uuid": "d4e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "index": 1, "kind": "invoice", "client_reference": "FACT-002", "document_uuid": null, "shipping_history_id": null, "status": "FAILED", "attempts": 1, "error_code": "404", "error_message": "No se encontró una resolución de facturación activa (resolution_number: 18764002566734, prefix: SETX).", "created_at": "2026-08-20T14:30:00.000000Z", "updated_at": "2026-08-20T14:30:08.000000Z", "processed_at": "2026-08-20T14:30:08.000000Z", "response": null, "item_id": "d4e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "kind_description": "Factura electrónica", "status_description": "Fallido", "error": { "code": "404", "message": "No se encontró una resolución de facturación activa (resolution_number: 18764002566734, prefix: SETX)." } } ], "first_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/b8f4155a-7ca2-11f0-be83-d843ae899220/items?page=1", "from": 1, "last_page": 1, "last_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/b8f4155a-7ca2-11f0-be83-d843ae899220/items?page=1", "links": [ { "url": null, "label": "« Previous", "active": false }, { "url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/b8f4155a-7ca2-11f0-be83-d843ae899220/items?page=1", "label": "1", "active": true }, { "url": null, "label": "Next »", "active": false } ], "next_page_url": null, "path": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/b8f4155a-7ca2-11f0-be83-d843ae899220/items", "per_page": 50, "prev_page_url": null, "to": 1, "total": 1 } ``` **GET /bulk/documents — Listar Todos los Lotes** ```http GET {{url}}/bulk/documents?page=1&per_page=15&status=PARTIAL&client_uuid={{client_uuid}} Authorization: Bearer {token} Accept: application/json ``` **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `status` | query | No | Filtrar por estado: `PENDING`, `PROCESSING`, `COMPLETED`, `PARTIAL`, `FAILED`. | | `per_page` | query | No | Lotes por página (default `15`, de 1 a 100). | | `page` | query | No | Número de página. | | `client_uuid` | query | No | UUID del cliente (Casa de Software). | Ordenados del más reciente al más antiguo. Cada lote trae sus datos sin `batch_id` ni `items` (el identificador es `uuid`). **✅ Respuesta Exitosa (HTTP 200)** ```json { "current_page": 1, "data": [ { "uuid": "b8f4155a-7ca2-11f0-be83-d843ae899220", "company_id": 1, "user_id": 5, "mode": "auto-increment", "stop_on_error": false, "total_items": 1, "succeeded": 1, "failed": 0, "status": "COMPLETED", "idempotency_key": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "finished_at": "2026-08-20T14:30:12.000000Z", "created_at": "2026-08-20T14:30:00.000000Z", "updated_at": "2026-08-20T14:30:12.000000Z" } ], "first_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents?page=1", "from": 1, "last_page": 1, "last_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents?page=1", "links": [ { "url": null, "label": "« Previous", "active": false }, { "url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents?page=1", "label": "1", "active": true }, { "url": null, "label": "Next »", "active": false } ], "next_page_url": null, "path": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents", "per_page": 15, "prev_page_url": null, "to": 1, "total": 1 } ``` --- # 💌 Marca Blanca de Correos :::info 🆕 Nuevo · ✅ Autenticación requerida Personaliza el logo, los colores y los textos de los correos que el API envía a los clientes finales en nombre de tus empresas. Disponible en todos los planes. ::: :::warning Autenticación Requerida Incluir en todos los endpoints de esta sección el header: `Authorization: Bearer {token}` ::: **¿Quién configura la marca?** - La **Casa de Software** (o la **cuenta directa**) configura la marca **con su propio token y sin `client_uuid`**. Todas sus sub-cuentas la heredan: configuras una vez y aplica a todos tus clientes. - Una **sub-cuenta** no puede definir su propia marca. Si la empresa resuelta (por el token o por `?client_uuid=`) es una sub-cuenta, `PUT`, `POST …/logo`, `DELETE …/logo` y `POST …/preview` responden HTTP 403: ```json { "success": false, "message": "La personalización de correos la define la casa de software a la que pertenece esta cuenta." } ``` - Con `?client_uuid={{client_uuid}}` de una sub-cuenta solo puedes **consultar** (`GET`) la marca que esa sub-cuenta hereda. **Tipos de correo `{type}`** (en mayúsculas exactas): | Tipo | Correos a los que aplica | |---|---| | `INVOICE` | Correo de los documentos electrónicos (facturas, notas, documentos soporte…): el que sale con la emisión y el de envío o reenvío con [`POST /documents/sendmail/{trackId}`](https://docs.matias-api.com/docs/endpoints/queries-status#correos-adquirentes). | | `EVENT` | Correos de eventos RADIAN. | | `CUSTOM` | Correo libre de [`POST /documents/sendmail/to`](https://docs.matias-api.com/docs/endpoints/queries-status#correos-adquirentes). | Cualquier otro valor responde HTTP 422 con `Tipo de correo inválido. Debe ser uno de: INVOICE, EVENT, CUSTOM.` :::caution 🆕 El tipo `CUSTOM` toma asunto, título y mensaje de la petición de envío En el correo de `POST /documents/sendmail/to`, el asunto es el campo `subject` de esa petición (por defecto `Titulo del correo`), el título es `title` y el cuerpo es `message`. De la marca `CUSTOM` solo se aplican al envío: - el logo; - `primary_color`, que solo cambia el color del título; - `footer_text`, que sustituye al pie de la plataforma; - `secondary_color`, que es el color de ese pie. `subject_template`, `greeting_text` y `body_text` de `CUSTOM` se guardan y se ven en la vista previa, pero **no se aplican al envío**. ::: :::note Diseño de la plataforma por defecto Si un tipo de correo no tiene marca configurada, o la tiene con `is_active: false`, el correo sale con el diseño y el logo de la plataforma. 🆕 Si la marca no se puede consultar al enviar, o su logo ya no está disponible, el correo se envía igual: con el diseño de la plataforma o, si el problema es el logo, con el logo de la plataforma. Un fallo al resolver la marca no bloquea el envío de la factura o del evento. ::: --- ## 📋 Consultar la Configuración **GET /settings/email-branding — Configuración de los Tres Tipos de Correo** ```http GET {{url}}/settings/email-branding?client_uuid={{client_uuid}} Authorization: Bearer {token} ``` Devuelve un elemento por tipo de correo con la configuración que **realmente se aplica** al enviar. Para una sub-cuenta, es la de su Casa de Software. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `client_uuid` | query | No | UUID de la empresa a consultar (Casa de Software). | **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecords": { "data": [ { "email_type": "INVOICE", "configured": true, "inherited": false, "editable": true, "is_active": true, "logo_asset_id": 31, "logo_url": "https://.../logo-marca.png", "properties": { "primary_color": "#0B5FFF", "secondary_color": "#F2F6FF", "border_color": "#D0D7E2" }, "primary_color": "#0B5FFF", "secondary_color": "#F2F6FF", "border_color": "#D0D7E2", "greeting_text": "

Hola {{recipient_name}},

", "body_text": "

Adjuntamos su {{document_type}} número {{document_number}} por {{document_total}}.

", "footer_text": "

{{issuer_trade_name}} · {{issuer_phone}} · {{issuer_website}}

", "subject_template": null, "allowed_variables": ["recipient_name", "recipient_tax_id", "recipient_email", "issuer_name", "document_number"], "variables": [ { "name": "recipient_name", "description": "Nombre del cliente que recibe el documento", "group": "Datos de quien recibe", "example": "CLIENTE DE EJEMPLO S.A.S." } ], "subject_editable": false, "applied_on_send": true }, { "email_type": "EVENT", "configured": false, "inherited": false, "editable": true, "is_active": false, "logo_asset_id": null, "logo_url": "https://.../logo-plataforma.png", "properties": null, "primary_color": null, "secondary_color": null, "border_color": null, "greeting_text": null, "body_text": null, "footer_text": null, "subject_template": null, "allowed_variables": ["recipient_name", "event_name", "document_number"], "variables": [ { "name": "event_name", "description": "Nombre del evento RADIAN (Acuse de recibo, Aceptación...)", "group": "Datos del evento", "example": "ACUSE DE RECIBO" } ], "subject_editable": false, "applied_on_send": false } ], "inherited_by": true }, "success": true } ``` El ejemplo está recortado: `data` trae siempre los tres tipos, y `allowed_variables` y `variables`, la lista completa del tipo. | Campo | Descripción | |---|---| | `configured` | `true` si el tipo tiene una configuración guardada. | | `inherited` | `true` si la configuración es la de la Casa de Software (la empresa consultada es una sub-cuenta). | | `editable` | `true` si la empresa consultada puede modificarla (es lo contrario de `inherited`). | | `is_active` / `applied_on_send` | Si la marca se aplica al enviar. | | `logo_url` | Logo que se usa hoy. Siempre es una URL utilizable: si no hay marca activa o logo propio, es el de la plataforma. | | `properties` | Colores agrupados (`primary_color`, `secondary_color`, `border_color`), o `null`. Los mismos colores se repiten en la raíz por compatibilidad. | | `allowed_variables` | Nombres de las variables que admite el tipo. | | `variables` | Las mismas variables con descripción, grupo y un ejemplo. | | `subject_editable` | `true` solo en `CUSTOM`, el único tipo en el que se puede guardar `subject_template`. Ese asunto guardado no se aplica al envío (ver la [advertencia sobre `CUSTOM`](https://docs.matias-api.com/docs/endpoints/email-branding#email-branding)). | | `inherited_by` | `true` si la empresa consultada tiene sub-cuentas (Casa de Software), que heredan su marca. | --- ## ✏️ Guardar la Personalización **PUT /settings/email-branding/{type} — Guardar Colores y Textos** ```http PUT {{url}}/settings/email-branding/{type} Authorization: Bearer {token} Content-Type: application/json ``` :::warning Reemplazo completo Cada `PUT` **reemplaza** la configuración del tipo; no es un merge. Un texto o un color que no envíes se guarda vacío, e `is_active` vuelve a `true` si no lo envías. Envía siempre la configuración completa. El logo no se toca: se gestiona con sus propios endpoints. ::: **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `type` | path | ✅ Sí | `INVOICE`, `EVENT` o `CUSTOM`. | **Body (JSON):** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `properties` | object | No | Colores, con lista cerrada de claves: `primary_color`, `secondary_color`, `border_color`. Cada uno en formato `#RRGGBB` o `null`. | | `greeting_text` | string (HTML) | No | Saludo. Máximo 2000 caracteres. | | `body_text` | string (HTML) | No | Cuerpo del mensaje. Máximo 10000 caracteres. | | `footer_text` | string (HTML) | No | Pie del correo. Máximo 4000 caracteres. Si tiene contenido, **reemplaza por completo** el pie de la plataforma (ver «Qué desaparece al personalizar el pie», más abajo). | | `subject_template` | string | No | Máximo 255 caracteres. **Solo se admite en `CUSTOM`**: en facturas y eventos el asunto lo define el formato exigido por la DIAN. Se guarda como texto plano de una sola línea y se ve en la vista previa, pero **no se aplica al envío**: el asunto del correo `CUSTOM` es el `subject` de `POST /documents/sendmail/to`. | | `is_active` | boolean | No | Si la marca se aplica al enviar (default: `true`). | Los colores también se aceptan en la raíz del body (`primary_color`, …) por compatibilidad. Si vienen en ambos sitios, prevalece `properties`. Los máximos se cuentan sobre el texto que envías. Son holgados porque el saneado añade atributos a cada enlace (unos 51 caracteres por enlace). **cURL** ```bash curl -X PUT "{{url}}/settings/email-branding/INVOICE" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d '{ "properties": { "primary_color": "#0B5FFF", "secondary_color": "#F2F6FF", "border_color": "#D0D7E2" }, "greeting_text": "

Hola {{recipient_name}},

", "body_text": "

Adjuntamos su {{document_type}} número {{document_number}} por {{document_total}}.

Consultar en la DIAN

", "footer_text": "

{{issuer_trade_name}} · {{issuer_phone}} · {{issuer_website}}

", "is_active": true }' ``` **JavaScript (Axios)** ```js const response = await axios.put(`${url}/settings/email-branding/INVOICE`, { properties: { primary_color: '#0B5FFF', secondary_color: '#F2F6FF', border_color: '#D0D7E2' }, greeting_text: '

Hola {{recipient_name}},

', body_text: '

Adjuntamos su {{document_type}} número {{document_number}} por {{document_total}}.

', footer_text: '

{{issuer_trade_name}} · {{issuer_phone}} · {{issuer_website}}

', is_active: true }, { headers: { Authorization: `Bearer ${token}` } }); ``` **PHP (Guzzle)** ```php $response = $client->put("{$url}/settings/email-branding/INVOICE", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'json' => [ 'properties' => [ 'primary_color' => '#0B5FFF', 'secondary_color' => '#F2F6FF', 'border_color' => '#D0D7E2', ], 'greeting_text' => '

Hola {{recipient_name}},

', 'body_text' => '

Adjuntamos su {{document_type}} número {{document_number}} por {{document_total}}.

', 'footer_text' => '

{{issuer_trade_name}} · {{issuer_phone}} · {{issuer_website}}

', 'is_active' => true, ], ]); ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Personalización guardada.", "dataRecord": { "id": 12, "company_id": 120, "email_type": "INVOICE", "logo_asset_id": 31, "properties": { "primary_color": "#0B5FFF", "secondary_color": "#F2F6FF", "border_color": "#D0D7E2" }, "greeting_text": "

Hola {{recipient_name}},

", "body_text": "

Adjuntamos su {{document_type}} número {{document_number}} por {{document_total}}.

", "footer_text": "

{{issuer_trade_name}} · {{issuer_phone}} · {{issuer_website}}

", "subject_template": null, "is_active": true, "created_at": "2026-10-21T13:00:00.000000Z", "updated_at": "2026-10-21T13:00:00.000000Z" }, "success": true } ``` Los textos se devuelven **ya saneados**, tal como saldrán en el correo. **❌ Errores de validación (HTTP 422)** Formato estándar de validación (`message` y `errors` por campo): ```json { "message": "El color principal debe tener el formato #RRGGBB.", "errors": { "properties.primary_color": ["El color principal debe tener el formato #RRGGBB."] } } ``` | Campo | Mensaje | |---|---| | `properties` | `Ajustes no reconocidos: . Los admitidos son: primary_color, secondary_color, border_color.` | | `properties.primary_color` | `El color principal debe tener el formato #RRGGBB.` | | `properties.secondary_color` | `El color secundario debe tener el formato #RRGGBB.` | | `properties.border_color` | `El color del borde debe tener el formato #RRGGBB.` | | `subject_template` | `El asunto solo puede personalizarse en los correos de tipo CUSTOM. En facturas y eventos lo define el formato exigido por la DIAN.` | | Cualquier texto | `Estas variables no existen para un correo de tipo : . Puedes usar: ` | **🎨 🆕 Dónde se aplica cada ajuste por tipo** No todos los ajustes tienen efecto en los tres tipos de correo: | Ajuste | `INVOICE` | `EVENT` | `CUSTOM` | |---|---|---|---| | Logo | ✅ | ✅ | ✅ | | `primary_color` | Título del documento, borde inferior del encabezado, fondo del recuadro del logo, saludo (`greeting_text`) y botón «Descargar documento del portal DIAN». | Solo el título (nombre del evento). | Solo el título. | | `border_color` | Borde del contenedor del correo. Si no se define, se mantiene el del diseño de la plataforma. | No se aplica. | No se aplica. | | `secondary_color` | Color del pie personalizado. | Color del pie personalizado. | Color del pie personalizado. | | `greeting_text` / `body_text` | Reemplazan **todo** el bloque por defecto (saludo, texto, «Documento Nº» y «Total»). | Reemplazan **por separado** el saludo y la frase por defecto. «Documento Nº» y «Total» se imprimen siempre. | No se aplican: el cuerpo es el `message` del envío. | | `footer_text` | ✅ Sustituye al pie de la plataforma. | ✅ Sustituye al pie de la plataforma. | ✅ Sustituye al pie de la plataforma. | | `subject_template` | No se admite. | No se admite. | Se guarda, pero no se aplica al envío. | - En `INVOICE`, si usas `greeting_text` o `body_text`, el correo deja de mostrar el número y el total por defecto. Inclúyelos tú con `{{document_number}}` y `{{document_total}}`. - En los tres tipos, `secondary_color` solo tiene efecto si hay `footer_text`. **🧾 🆕 Qué desaparece al personalizar el pie** Cuando `footer_text` tiene contenido, el pie de la plataforma desaparece entero en `INVOICE`, `EVENT` y `CUSTOM`. Son tres partes: 1. El aviso para notificar acuse de recibo, aceptación o rechazo, con la dirección de correo de la empresa emisora. 2. «Estimado cliente, este correo es generado automáticamente, por favor no responda a este mensaje». 3. «Correo generado por: razón social», con enlace al sitio web de la empresa. Te recomendamos un pie equivalente con variables: ```html

Para notificar acuse de recibo, aceptación o rechazo, escriba a {{issuer_email}}

Este correo es automático, por favor no responda a este mensaje.

Enviado por {{issuer_name}}

``` El recuadro final con los datos de la empresa emisora (razón social y NIT, dirección, correo, teléfonos y su logo) no forma parte del pie: se mantiene siempre. **🧹 HTML admitido en los textos** Los textos llegan de un editor enriquecido y se **sanean antes de guardar**: - **Etiquetas que se conservan:** `p`, `br`, `b`, `strong`, `i`, `em`, `u`, `s`, `blockquote`, `h3`, `h4`, `ul`, `ol`, `li`, `a` (con `href`, `title` y `target`) y `span` (con `style`). - **Estilos en línea admitidos en `span`:** `color`, `font-weight`, `font-style`, `text-decoration` y `text-align`. - Todo lo demás (imágenes, tablas, scripts, iframes, otros estilos) se elimina. - 🆕 **Enlaces:** en `href` solo se admiten los esquemas `http`, `https` y `mailto`. Un `href` con `javascript:` o `data:` se elimina, igual que los atributos de evento (`onerror`, `onclick`…). Cada enlace sale con `target="_blank"` y `rel="nofollow noreferrer noopener"`. - 🆕 Las variables funcionan también dentro de `href`, por ejemplo ``. Dentro de `href`, escríbelas sin espacios entre las llaves. - 🆕 El marcado mal cerrado se repara en lugar de rechazarse: `

Holanegrita` se guarda como `

Holanegrita

`. - 🆕 Un texto que queda sin contenido visible tras sanear (por ejemplo, solo ``) se guarda como cadena vacía. - Los saltos de línea se conservan: los de un textarea se convierten en `
`. - `subject_template` se guarda siempre como texto plano de una sola línea. **🔤 Variables disponibles por tipo** Escribe las variables con doble llave, por ejemplo `{{recipient_name}}`. Al enviar, se reemplazan por los datos reales del documento. Usar una variable que no pertenece al tipo produce un error 422 al guardar. | Variable | Significado | `INVOICE` | `EVENT` | `CUSTOM` | |---|---|:-:|:-:|:-:| | `recipient_name` | Nombre del cliente que recibe el documento | ✅ | ✅ | ✅ | | `recipient_tax_id` | NIT o documento de identidad del cliente | ✅ | | | | `recipient_email` | Correo del cliente al que se envía | ✅ | | | | `issuer_name` | Razón social de la empresa que emite | ✅ | ✅ | ✅ | | `issuer_trade_name` | Nombre comercial del emisor (o la razón social si no tiene) | ✅ | ✅ | ✅ | | `issuer_tax_id` | NIT de la empresa que emite | ✅ | ✅ | ✅ | | `issuer_tax_id_dv` | Dígito de verificación del NIT del emisor | ✅ | ✅ | | | `issuer_address` | Dirección de la empresa que emite | ✅ | ✅ | ✅ | | `issuer_phone` | Teléfono fijo de la empresa que emite | ✅ | ✅ | ✅ | | `issuer_mobile` | Celular de la empresa que emite | ✅ | ✅ | ✅ | | `issuer_email` | Correo de contacto de la empresa que emite | ✅ | ✅ | ✅ | | `issuer_website` | Sitio web de la empresa que emite | ✅ | ✅ | ✅ | | `document_type` | Tipo de documento (Factura electrónica de venta, Nota crédito…) | ✅ | | | | `document_number` | Número del documento | ✅ | ✅ | | | `document_total` | Valor total, ya formateado con símbolo y separadores | ✅ | ✅ | | | `document_dian_url` | Enlace para consultar el documento en el portal de la DIAN | ✅ | | | | `event_name` | Nombre del evento RADIAN (Acuse de recibo, Aceptación…) | | ✅ | | | `email_title` | Título indicado al enviar el correo personalizado | | | ✅ | `GET /settings/email-branding` devuelve esta misma lista por tipo, con un ejemplo de cada valor en `variables[].example`. El campo `group` toma los valores `Datos de quien recibe`, `Datos de quien emite`, `Datos del documento`, `Datos del evento` y `Datos del correo`. 🆕 **Detalles de las variables:** - En el texto se admiten espacios dentro de las llaves: `{{ recipient_name }}` equivale a `{{recipient_name}}`. Dentro de `href`, no. - `document_total` llega ya formateado con el símbolo de la moneda, coma de miles y punto decimal (por ejemplo, `$ 1,290,000.00`). No hay que reformatearlo. El valor de ejemplo de la vista previa y de `variables[].example` (`$ 1.290.000,00`) es solo ilustrativo. - `document_dian_url` puede quedar vacía cuando el documento no tiene URL de consulta en la DIAN. --- ## 🖼️ Logo de la Marca **POST /settings/email-branding/{type}/logo — Subir Logo** ```http POST {{url}}/settings/email-branding/{type}/logo Authorization: Bearer {token} Content-Type: multipart/form-data ``` Sube el logo que se muestra en los correos del tipo indicado. :::warning 🆕 Un solo logo de correo por empresa Cada empresa tiene **un solo logo de correo vigente**. Al subir un logo para un tipo se borra el logo anterior de la empresa, sea cual sea el tipo al que pertenecía. Los demás tipos que tenían logo propio quedan con `logo_asset_id: null` y vuelven al logo de la plataforma. Subir el mismo logo tres veces no sirve para usarlo en `INVOICE`, `EVENT` y `CUSTOM`: solo conserva su logo el último tipo al que se subió. ::: Si el tipo aún no tiene configuración guardada, la API la crea activa. El `GET` muestra entonces `configured: true`, `is_active: true` y `applied_on_send: true`, sin colores ni textos, y el correo sale con el logo propio y el resto del diseño de la plataforma. Un `PUT` posterior reemplaza colores y textos y conserva el logo. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `type` | path | ✅ Sí | `INVOICE`, `EVENT` o `CUSTOM`. | | `logo` | form-data (archivo) | ✅ Sí | Imagen PNG, JPEG o GIF de hasta **512 KB**. El formato se comprueba por el contenido real del archivo, no por su extensión. | **cURL** ```bash curl -X POST "{{url}}/settings/email-branding/INVOICE/logo" \ -H "Authorization: Bearer {token}" \ -F "logo=@logo.png" ``` **JavaScript (Axios)** ```js const form = new FormData(); form.append('logo', fileInput.files[0]); // PNG, JPG o GIF, máx. 512 KB const response = await axios.post(`${url}/settings/email-branding/INVOICE/logo`, form, { headers: { Authorization: `Bearer ${token}` } }); const logoUrl = response.data.dataRecord.logo_url; ``` **PHP (Guzzle)** ```php $response = $client->post("{$url}/settings/email-branding/INVOICE/logo", [ 'headers' => ['Authorization' => "Bearer {$token}"], 'multipart' => [ ['name' => 'logo', 'contents' => fopen('logo.png', 'r'), 'filename' => 'logo.png'], ], ]); $logoUrl = json_decode($response->getBody())->dataRecord->logo_url; ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Logo actualizado.", "dataRecord": { "logo_asset_id": 31, "logo_url": "https://.../logo-marca.png", "email_type": "INVOICE" }, "success": true } ``` `logo_asset_id`, `logo_url` y `email_type` corresponden al tipo al que se subió el logo. **❌ Errores (HTTP 422)** ```json { "success": false, "message": "El logo supera el tamaño máximo de 512 KB." } ``` | Mensaje | Causa | |---|---| | `No se recibió ningún archivo en el campo "logo".` | Falta el campo `logo`. | | `El archivo del logo no se recibió correctamente.` | La carga del archivo falló. | | `El logo supera el tamaño máximo de 512 KB.` | El archivo pesa más de 512 KB. | | `Formato de logo no admitido (). Use PNG, JPG o GIF.` | El contenido no es PNG, JPEG ni GIF. | | `El archivo no es una imagen válida.` | El archivo está dañado o no es una imagen. | **DELETE /settings/email-branding/{type}/logo — Quitar Logo** ```http DELETE {{url}}/settings/email-branding/{type}/logo Authorization: Bearer {token} ``` Quita el logo propio del tipo indicado. Desde ese momento, los correos usan el logo de la plataforma. Colores y textos no cambian. **Parámetros:** | Nombre | Ubicación | Requerido | Descripción | |---|---|---|---| | `type` | path | ✅ Sí | `INVOICE`, `EVENT` o `CUSTOM`. | **✅ Respuesta Exitosa (HTTP 200)** ```json { "message": "Logo retirado; se usará el de la plataforma.", "logo_url": "https://.../logo-plataforma.png", "success": true } ``` --- ## 👁️ Vista Previa **POST /settings/email-branding/{type}/preview — Previsualizar con Datos de Ejemplo** ```http POST {{url}}/settings/email-branding/{type}/preview Authorization: Bearer {token} Content-Type: application/json ``` Muestra cómo quedarían los textos con datos de ejemplo, **sin guardar nada**. Aplica el mismo saneado que al guardar, así que ves exactamente lo que se enviaría. 🆕 **La vista previa sanea, pero no valida:** - No rechaza variables desconocidas: quedan literales en `rendered`. El error 422 de variables solo lo da el `PUT`. - No comprueba longitudes ni colores. - En tipos distintos de `CUSTOM`, `subject_template` se ignora y `rendered.subject_template` es `null`. - Responde HTTP 403 si la empresa es una sub-cuenta y HTTP 422 si el tipo no es válido. - En `CUSTOM`, recuerda que `greeting_text`, `body_text` y `subject_template` no se aplican al envío real (ver la [advertencia sobre `CUSTOM`](https://docs.matias-api.com/docs/endpoints/email-branding#email-branding)). **Body (JSON, opcional):** `greeting_text`, `body_text`, `footer_text` y `subject_template` (este último solo en `CUSTOM`). Lo que no envíes se toma de la configuración activa guardada. ```json { "body_text": "

Adjuntamos su {{document_type}} número {{document_number}}.

" } ``` **✅ Respuesta Exitosa (HTTP 200)** ```json { "dataRecord": { "email_type": "INVOICE", "sample_data": { "recipient_name": "CLIENTE DE EJEMPLO S.A.S.", "recipient_tax_id": "901234567", "recipient_email": "cliente@ejemplo.com", "document_number": "SETP990000001" }, "rendered": { "greeting_text": "

Hola CLIENTE DE EJEMPLO S.A.S.,

", "body_text": "

Adjuntamos su FACTURA ELECTRÓNICA DE VENTA número SETP990000001.

", "footer_text": null, "subject_template": null }, "branding": { "logo_url": "https://.../logo-marca.png", "primary_color": "#0B5FFF", "secondary_color": "#F2F6FF", "border_color": "#D0D7E2", "greeting_text": "

Hola {{recipient_name}},

", "body_text": null, "footer_text": null, "subject_template": null, "is_custom": true } }, "success": true } ``` | Campo | Descripción | |---|---| | `sample_data` | Valores de ejemplo de las variables del tipo (recortado en el ejemplo). | | `rendered` | Textos con las variables reemplazadas por los valores de ejemplo. | | `branding` | Marca activa guardada para el tipo (logo, colores y textos guardados, con las variables sin reemplazar). Si no hay marca activa, trae los valores de la plataforma con `is_custom: false`. | --- # Envío Masivo de Documentos (Bulk) :::info Versión Disponible desde **v2.10.0** · Base path: `/api/ubl2.1/bulk/documents` · Referencia rápida de endpoints: [📦 Envío Masivo (Bulk)](https://docs.matias-api.com/docs/endpoints/bulk-documents.md) ¿Ya integrabas el envío masivo? Revisa [Cambios y compatibilidad](https://docs.matias-api.com/docs/bulk-documents#cambios-y-compatibilidad) 🆕. ::: :::warning 🚧 Endpoint en Fase BETA El envío masivo de documentos (Bulk) se encuentra actualmente en **Fase BETA**. Aunque es completamente funcional, esto conlleva que los límites de rate limit, las estructuras de respuesta asíncrona, políticas de retención o las ventanas temporales de procesamiento podrían recibir ajustes técnicos orientados a estabilizar la infraestructura. Te sugerimos implementar primero tu flujo de integración apoyándote en el ambiente [Sandbox](https://docs.matias-api.com/docs/sandbox/quickstart.md) antes de liberar a usuarios finales en producción. ::: :::danger ⚠️ IMPORTANTE: el lote se elimina a los 7 días Los lotes y sus items son **temporales**. Un lote terminado (`COMPLETED`, `PARTIAL` o `FAILED`) se **elimina por completo a los 7 días de su creación**, sin posibilidad de recuperación. Ver [Retención de datos](https://docs.matias-api.com/docs/bulk-documents#retencion). Cada item que llegó a emitirse retorna un **`document_uuid`**. **Extrae y guarda este UUID en tu sistema** mientras el lote se procesa o apenas termine. Si el lote ya se purgó y no guardaste el UUID, **aún puedes consultar el documento con el [endpoint de búsqueda](https://docs.matias-api.com/docs/endpoints/queries-status#busqueda-listado) usando el número y el prefijo**. ::: El endpoint de envío masivo permite procesar **múltiples documentos electrónicos** en un solo request HTTP. Los documentos se encolan para procesamiento asíncrono y puedes consultar el estado de cada uno individualmente. --- ## Flujo General 1. 🖥️ Tu sistema envía `POST /bulk/documents` con N documentos. 2. ⚡ La API valida el request y la cuota, registra el lote y sus items y responde `201 Created` con el `batch_id`. 3. 📦 Cada documento se procesa por separado: se envía a la 🏛️ DIAN en UBL 2.1 y su item se actualiza con la respuesta. 4. Cuando todos los items terminan, el lote se cierra y se dispara el webhook `bulk.batch.completed`. 5. Consultas `GET /bulk/documents/{batch_id}` para ver el estado completo del lote. --- ## Autenticación Todos los endpoints requieren autenticación vía **Bearer Token** (Personal Access Token): ``` Authorization: Bearer {tu-token} Accept: application/json ``` - **Casas de Software:** agrega `?client_uuid={{client_uuid}}` para crear y consultar lotes en nombre de una empresa cliente. El lote queda asociado a esa empresa y solo se consulta con el mismo `client_uuid`. - **`Accept: application/json`:** envíalo siempre. Sin este header, algunos errores (validación, límites) pueden llegar como redirección `302` o como página HTML en lugar de JSON. :::warning Membresía requerida también para consultar Todo el grupo `/bulk` exige una membresía con cupo, no solo el `POST`. Si la empresa agotó su cuota o su membresía está inactiva, **también** `GET /bulk/documents`, `GET /bulk/documents/{batch_id}` y `GET /bulk/documents/{batch_id}/items` responden `402`: ```json { "success": false, "message": "Límite de documentos de la membresía alcanzado o membresía inactiva." } ``` Si el `client_uuid` no corresponde a una empresa cliente activa vinculada a tu cuenta, la petición se rechaza antes de llegar al lote (hoy con un `500` y `{"success": false, "message": "Error interno al verificar la membresía."}`). Revisa el `client_uuid` antes de reintentar. Por eso conviene **guardar el `document_uuid` de cada item mientras el lote se procesa**. Si te quedas sin cupo, consulta tus documentos por `GET /documents` o `GET /status` ([Consultas y Estados](https://docs.matias-api.com/docs/endpoints/queries-status.md)). ::: --- ## Límites por Plan | Plan | Docs por lote | Lotes por minuto | Descripción | |------|:------------:|:----------:|-------------| | **FREE** | 5 | 1 | Plan gratuito con límite diario | | **Pago** | 50 | 10 | Suscripción con cuota finita | | **Ilimitado** | 200 | 60 | Plan Servidor / Enterprise | - **Tope técnico:** ningún lote puede superar **200 documentos**, sea cual sea el plan. - **Docs por lote:** si el lote supera el tope de tu plan, la API responde `413` antes de validar el resto del body. - **Lotes por minuto:** el límite se cuenta **por usuario y por empresa**: varios usuarios de la misma empresa comparten el cupo. - **Cuota:** el lote completo debe caber en la cuota disponible de la suscripción, descontando los documentos que aún estén pendientes de lotes anteriores. Si no cabe, responde `402` y no se encola ningún documento. - **Documentos en proceso:** una empresa puede tener como máximo **1000 documentos en proceso** (en cola o procesándose) a la vez. Por encima responde `429` con `Retry-After`. Ver [Códigos de Error HTTP](https://docs.matias-api.com/docs/bulk-documents#errores) para el formato de cada respuesta. --- ## Tipos de Documento Soportados | Valor (`kind`) | Descripción | Endpoint unitario equivalente (`auto-increment`) | Endpoint unitario equivalente (`manual`) | |:--------------:|-------------|-------------------------------|-------------------------------| | `invoice` | Factura electrónica | `POST /auto-increment/invoices` | `POST /invoice` | | `credit_note` | Nota crédito | `POST /auto-increment/credit-notes` | `POST /notes/credit` | | `debit_note` | Nota débito | `POST /auto-increment/debit-notes` | `POST /notes/debit` | | `support_document` | Documento soporte | `POST /auto-increment/support-documents` | `POST /ds/document` | | `adjustment_note` | Nota de ajuste | `POST /auto-increment/adjustment-notes` | `POST /ds/adjustment-note` | | `pos_document` | Documento POS | `POST /auto-increment/pos-documents` | `POST /invoice` | ### Compatibilidad de payload El `payload` de cada documento usa el **mismo formato JSON** que el body del endpoint unitario equivalente. Si ya tienes integración con el endpoint unitario, puedes reutilizar el mismo JSON. Hay diferencias importantes: - **El payload no pasa por las validaciones del endpoint unitario.** Al admitir el lote solo se verifica que `payload` sea un objeto JSON. Un dato inválido **no** produce un `422` con `errors`: el lote se acepta y ese item termina `FAILED` con el error que devuelva el procesamiento (ver [Catálogo de `error_code`](https://docs.matias-api.com/docs/bulk-documents#error-code)). Los errores internos (`5xx`) se reintentan antes de quedar `FAILED`. - **Valida tu JSON antes de enviarlo masivamente**, por ejemplo probándolo una vez en el endpoint unitario o en el [Sandbox](https://docs.matias-api.com/docs/sandbox/quickstart.md). - **La empresa emisora es siempre la del lote** (la del token y el `client_uuid` del `POST /bulk/documents`). Un `client_uuid` o `companyId` dentro del `payload` se descarta. - **`payload.uuid` se reemplaza** por el UUID anti-duplicados del item (ver [`documents[].uuid`](https://docs.matias-api.com/docs/bulk-documents#uuid-anti-duplicados)). --- ## Endpoints ### 1. Crear Lote ``` POST /api/ubl2.1/bulk/documents ``` Acepta un lote de documentos para procesamiento asíncrono. Retorna inmediatamente con **`201 Created`**. #### Headers | Header | Tipo | Requerido | Descripción | |--------|------|:---------:|-------------| | `Authorization` | string | ✅ | `Bearer {token}` | | `Content-Type` | string | ✅ | `application/json` | | `Accept` | string | Recomendado | `application/json`, para recibir todos los errores en JSON. | | `Idempotency-Key` | UUID v4 | ❌ | Clave de idempotencia (máx. 36 caracteres, válida 24 h). Ver [Idempotencia](https://docs.matias-api.com/docs/bulk-documents#idempotencia). | #### Query Parameters | Parámetro | Tipo | Requerido | Descripción | |-----------|------|:---------:|-------------| | `client_uuid` | UUID | ❌ | Empresa cliente en cuyo nombre se emite el lote (Casas de Software). | #### Request Body | Campo | Tipo | Requerido | Descripción | |-------|------|:---------:|-------------| | `mode` | string | ✅ | `auto-increment` o `manual`. Ver [Modos de envío](https://docs.matias-api.com/docs/bulk-documents#modos-de-envio). | | `stop_on_error` | boolean | ❌ | Si `true`, el primer documento que no termine en `SUCCESS` cancela los items que siguen en cola. Default: `false`. | | `default_resolution_number` | string | ❌ | Número de resolución por defecto (máx. 50). Se copia en `payload.resolution_number` de los documentos que no lo traigan. | | `default_prefix` | string | ❌ | Prefijo por defecto (máx. 10). Se copia en `payload.prefix` de los documentos que no lo traigan. | | `documents` | array | ✅ | Documentos del lote. Mínimo 1; máximo el tope de tu plan y nunca más de 200. | | `documents[].kind` | string | ✅ | Tipo de documento. Ver [tipos soportados](https://docs.matias-api.com/docs/bulk-documents#tipos-de-documento-soportados). | | `documents[].payload` | object | ✅ | Body del endpoint unitario equivalente. Ver [Compatibilidad de payload](https://docs.matias-api.com/docs/bulk-documents#compatibilidad-payload). | | `documents[].uuid` | UUID | ❌ | UUID anti-duplicados del documento. Debe ser válido y distinto dentro del lote. Ver [UUID anti-duplicados](https://docs.matias-api.com/docs/bulk-documents#uuid-anti-duplicados). | | `documents[].client_reference` | string | ❌ | Tu referencia interna para rastrear el documento (máx. 64 caracteres). | | `documents[].attachments` | array | ❌ | Adjuntos del documento (máx. 4). Se copian en `payload.attachments` solo si el `payload` no trae adjuntos propios: si `payload.attachments` existe y no está vacío, estos se ignoran. Ver [Adjuntos](https://docs.matias-api.com/docs/bulk-documents#adjuntos). | | `documents[].attachments[].filename` | string | ✅ (por adjunto) | Nombre del archivo (máx. 255). | | `documents[].attachments[].content` | string | ✅ (por adjunto) | Contenido del archivo en base64. | | `documents[].attachments[].content_type` | string | ❌ | Tipo MIME (máx. 100). | #### Adjuntos - Los adjuntos de `documents[].attachments` viajan dentro del `payload` (`payload.attachments`) y se guardan y vinculan al documento igual que en el endpoint unitario, con la empresa del lote. - Si el `payload` ya trae `attachments` con contenido, **manda el del `payload`** y `documents[].attachments` se ignora. Envía los adjuntos en uno solo de los dos lugares. - Si el documento se emite pero sus adjuntos no quedan vinculados, el item lleva el warning `ATTACHMENTS_NOT_LINKED` (ver [El campo `response`](https://docs.matias-api.com/docs/bulk-documents#campo-response)). :::warning Cada documento va envuelto en `kind` + `payload` Los campos del documento (`type_document_id`, `customer`, `lines`…) van **dentro de `payload`**, nunca directamente en `documents[]`. Un documento sin `kind` o sin `payload` hace que todo el lote se rechace con `422` (`Cada documento debe especificar su tipo (kind).` / `Cada documento debe incluir su payload.`). ::: #### UUID anti-duplicados Cada item se emite con un UUID que impide emitir dos veces el mismo documento: 1. `documents[].uuid`, si lo envías. 2. Si no, `payload.uuid`, solo si es un UUID válido (si no lo es, se ignora). 3. Si tampoco, el `item_id` del propio item. - `documents[].uuid` debe ser un UUID válido y no repetirse dentro del lote (sin distinguir mayúsculas). Si no se cumple, **todo el lote** se rechaza con `422` (`El UUID del documento debe ser un UUID v4 válido.` / `El UUID del documento está repetido dentro del lote.`). - El UUID se registra antes de transmitir. Si la empresa ya tiene otro documento con ese UUID, el item **no se emite** y termina `FAILED` con `error_code` `DUPLICATE_DOCUMENT_UUID` y el mensaje `El uuid … ya se usó en el documento …. No se emitió de nuevo.`. Su `document_uuid` apunta al documento existente. - Si el item falla antes de transmitirse, el UUID queda libre y puedes reenviar el documento con el mismo UUID. #### Ejemplos de Request Los payloads de estos ejemplos están **abreviados**: usa el JSON completo del endpoint unitario (ver, por ejemplo, [Factura con varios impuestos](https://docs.matias-api.com/docs/jsons-billing/invoice-with-taxes.md)). **Auto-increment** ```json { "mode": "auto-increment", "stop_on_error": false, "default_resolution_number": "18764002566734", "default_prefix": "SETT", "documents": [ { "kind": "invoice", "uuid": "6f1c2b9e-3d4a-4e8b-9c71-2a5d8e0f4b13", "client_reference": "FACT-001", "payload": { "type_document_id": 7, "operation_type_id": 1, "send_email": 0, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "1190.00" } ], "customer": { "dni": "1234567890", "company_name": "CLIENTE DE PRUEBA", "email": "facturacion@ejemplo.com" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "1000.00", "free_of_charge_indicator": false, "description": "Servicio de consultoría", "code": "SRV-001", "price_amount": "1000.00", "base_quantity": "1", "tax_totals": [ { "tax_id": "1", "tax_amount": 190.00, "taxable_amount": 1000.00, "percent": 19 } ] } ], "legal_monetary_totals": { "line_extension_amount": "1000.00", "tax_exclusive_amount": "1000.00", "tax_inclusive_amount": "1190.00", "payable_amount": "1190.00" } } }, { "kind": "invoice", "client_reference": "FACT-002", "payload": { "type_document_id": 7, "...": "resto del payload, igual que en el endpoint unitario" } } ] } ``` **Tipos mixtos** ```json { "mode": "auto-increment", "stop_on_error": true, "documents": [ { "kind": "invoice", "client_reference": "FACT-100", "payload": { "type_document_id": 7, "resolution_number": "18764002566734", "prefix": "SETT", "customer": { "...": "payload completo de factura" }, "lines": ["..."] } }, { "kind": "credit_note", "client_reference": "NC-100", "payload": { "type_document_id": 5, "resolution_number": "18764002566735", "prefix": "NC", "billing_reference": { "number": "SETT990000001", "date": "2026-06-01", "uuid": "CUFE-de-la-factura-referenciada" }, "customer": { "...": "payload completo de nota crédito" }, "lines": ["..."] } }, { "kind": "support_document", "client_reference": "DS-100", "payload": { "type_document_id": 11, "resolution_number": "18764002566999", "prefix": "SEDS", "customer": { "...": "payload completo de documento soporte" }, "lines": ["..."] } } ] } ``` #### Response `201 Created` ```json { "message": "Lote aceptado para procesamiento asíncrono.", "batch_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "PENDING", "status_url": "/api/ubl2.1/bulk/documents/f47ac10b-58cc-4372-a567-0e02b2c3d479", "received": 2, "accepted": 2, "rejected": 0, "estimated_completion_seconds": 12, "items": [ { "index": 0, "client_reference": "FACT-001", "item_id": "c1d2e3f4-a1b2-c3d4-e5f6-a7b8c9d0e1f2", "status": "QUEUED" }, { "index": 1, "client_reference": "FACT-002", "item_id": "d4e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "status": "QUEUED" } ], "success": true } ``` | Campo | Descripción | |-------|-------------| | `batch_id` | UUID del lote. Úsalo para consultar su estado. | | `status` | Estado del lote (`PENDING` al crearlo). En un replay idempotente, el estado actual del lote original. | | `status_url` | Ruta relativa para consultar el lote. | | `received` / `accepted` / `rejected` | Documentos recibidos y aceptados. El lote se acepta completo o se rechaza completo, así que `rejected` siempre es `0`. | | `estimated_completion_seconds` | Estimación de segundos hasta que termine el lote, según la cola actual. `0` si el lote ya terminó (replay). | | `items[]` | Un registro por documento con su `index` (posición en `documents[]`, desde 0), `client_reference`, `item_id` y `status`. | --- ### 2. Consultar Lote ``` GET /api/ubl2.1/bulk/documents/{batch_id} ``` Retorna el estado completo del lote con todos sus items, ordenados por `index`. #### Parámetros de ruta | Parámetro | Tipo | Descripción | |-----------|------|-------------| | `batch_id` | UUID | Identificador del lote (retornado en el POST) | #### Response `200 OK` ```json { "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "company_id": 1, "user_id": 5, "mode": "auto-increment", "stop_on_error": false, "total_items": 2, "succeeded": 1, "failed": 1, "status": "PARTIAL", "idempotency_key": "550e8400-e29b-41d4-a716-446655440000", "finished_at": "2026-06-01T19:31:00.000000Z", "created_at": "2026-06-01T19:30:00.000000Z", "updated_at": "2026-06-01T19:31:00.000000Z", "batch_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status_description": "Completado parcialmente", "received": 2, "items": [ { "uuid": "c1d2e3f4-a1b2-c3d4-e5f6-a7b8c9d0e1f2", "index": 0, "kind": "invoice", "client_reference": "FACT-001", "document_uuid": "3e7a9c51-8b2d-4f6e-a1c4-9d0b2e5f7a38", "shipping_history_id": 4200027, "status": "SUCCESS", "attempts": 1, "error_code": null, "error_message": null, "created_at": "2026-06-01T19:30:00.000000Z", "updated_at": "2026-06-01T19:30:05.000000Z", "processed_at": "2026-06-01T19:30:05.000000Z", "response": "{\"uuid\":\"3e7a9c51-8b2d-4f6e-a1c4-9d0b2e5f7a38\",\"document_number\":\"SETT990000001\",\"XmlDocumentKey\":\"cf9864294501e8a9578235dd2ab3c4fd\",\"cufe\":\"cf9864294501e8a9578235dd2ab3c4fd\",\"track_id\":null,\"is_valid\":true,\"status_code\":200,\"dian_status_code\":\"00\",\"dian_status\":\"ACCEPTED\",\"errors\":[],\"notifications\":[],\"warnings\":[],\"http_status\":200}", "item_id": "c1d2e3f4-a1b2-c3d4-e5f6-a7b8c9d0e1f2", "kind_description": "Factura electrónica", "status_description": "Exitoso", "document_number": "SETT990000001", "cufe": "cf9864294501e8a9578235dd2ab3c4fd", "track_id": null, "dian_response": { "is_valid": true, "status_code": "00", "status": "ACCEPTED", "errors": [], "notifications": [] }, "warnings": [] }, { "uuid": "d4e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "index": 1, "kind": "invoice", "client_reference": "FACT-002", "document_uuid": null, "shipping_history_id": null, "status": "FAILED", "attempts": 1, "error_code": "404", "error_message": "No se encontró una resolución de facturación activa (resolution_number: 18764002566734, prefix: SETX).", "created_at": "2026-06-01T19:30:00.000000Z", "updated_at": "2026-06-01T19:30:08.000000Z", "processed_at": "2026-06-01T19:30:08.000000Z", "response": null, "item_id": "d4e5f6a7-b8c9-d0e1-f2a3-b4c5d6e7f8a9", "kind_description": "Factura electrónica", "status_description": "Fallido", "error": { "code": "404", "message": "No se encontró una resolución de facturación activa (resolution_number: 18764002566734, prefix: SETX)." } } ], "success": true } ``` Ver [Campos de cada item](https://docs.matias-api.com/docs/bulk-documents#campos-item) para el significado de cada campo según el estado. :::note Lote inexistente Si el `batch_id` no existe, ya se purgó o pertenece a otra empresa, la API responde `404` con un único campo `message` (sin `success`). Sin el header `Accept: application/json` la respuesta puede ser una página HTML. ::: --- ### 3. Listar Items del Lote ``` GET /api/ubl2.1/bulk/documents/{batch_id}/items ``` Lista los items de un lote, ordenados por `index`, con filtros opcionales y paginación estándar (`current_page`, `data`, `per_page`, `total`, `links`…). Cada elemento de `data` tiene los mismos campos que `items[]` en [Consultar Lote](https://docs.matias-api.com/docs/bulk-documents#get-batch-status). #### Query Parameters | Parámetro | Tipo | Default | Descripción | |-----------|------|:-------:|-------------| | `status` | string | — | Filtrar: `QUEUED`, `PROCESSING`, `SUCCESS`, `FAILED`, `REJECTED_BY_DIAN`, `POSSIBLY_SENT`, `CANCELLED` | | `kind` | string | — | Filtrar: `invoice`, `credit_note`, `debit_note`, `support_document`, `adjustment_note`, `pos_document` | | `per_page` | integer | `50` | Items por página (de 1 a 200) | | `page` | integer | `1` | Número de página | #### Response `200 OK` ```json { "current_page": 1, "data": [ { "uuid": "e5f6a7b8-c9d0-e1f2-a3b4-c5d6e7f8a9b0", "index": 2, "kind": "invoice", "client_reference": "FACT-003", "document_uuid": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "shipping_history_id": 4200029, "status": "POSSIBLY_SENT", "attempts": 1, "error_code": "DOCUMENT_POSSIBLY_SENT", "error_message": "La respuesta no trae un resultado DIAN reconocible. Consulte el estado del envío antes de reenviar.", "created_at": "2026-06-01T19:30:00.000000Z", "updated_at": "2026-06-01T19:30:40.000000Z", "processed_at": "2026-06-01T19:30:40.000000Z", "response": "{\"uuid\":\"8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d\",\"document_number\":\"SETT990000003\",\"XmlDocumentKey\":null,\"cufe\":null,\"track_id\":null,\"is_valid\":null,\"status_code\":200,\"dian_status_code\":null,\"dian_status\":\"POSSIBLY_SENT\",\"errors\":[],\"notifications\":[],\"warnings\":[],\"http_status\":200}", "item_id": "e5f6a7b8-c9d0-e1f2-a3b4-c5d6e7f8a9b0", "kind_description": "Factura electrónica", "status_description": "Sin respuesta definitiva de la DIAN", "document_number": "SETT990000003", "cufe": null, "track_id": null, "dian_response": { "is_valid": null, "status_code": null, "status": "POSSIBLY_SENT", "errors": [], "notifications": [] }, "warnings": [], "error": { "code": "DOCUMENT_POSSIBLY_SENT", "message": "El documento pudo llegar a la DIAN sin respuesta definitiva. Consulte su estado con el document_uuid antes de cualquier acción; no lo reenvíe." }, "next_action": "CHECK_STATUS" } ], "first_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/f47ac10b-58cc-4372-a567-0e02b2c3d479/items?page=1", "from": 1, "last_page": 1, "last_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/f47ac10b-58cc-4372-a567-0e02b2c3d479/items?page=1", "links": [ { "url": null, "label": "« Previous", "active": false }, { "url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/f47ac10b-58cc-4372-a567-0e02b2c3d479/items?page=1", "label": "1", "active": true }, { "url": null, "label": "Next »", "active": false } ], "next_page_url": null, "path": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents/f47ac10b-58cc-4372-a567-0e02b2c3d479/items", "per_page": 50, "prev_page_url": null, "to": 1, "total": 1 } ``` --- ### 4. Listar Lotes ``` GET /api/ubl2.1/bulk/documents ``` Lista los lotes de la compañía, ordenados del más reciente al más antiguo, con paginación estándar. Cada elemento de `data` trae los datos del lote **sin** `batch_id`, `status_description` ni `items` (el identificador del lote es `uuid`). #### Query Parameters | Parámetro | Tipo | Default | Descripción | |-----------|------|:-------:|-------------| | `status` | string | — | Filtrar: `PENDING`, `PROCESSING`, `COMPLETED`, `PARTIAL`, `FAILED` | | `per_page` | integer | `15` | Lotes por página (de 1 a 100) | | `page` | integer | `1` | Número de página | #### Response `200 OK` ```json { "current_page": 1, "data": [ { "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "company_id": 1, "user_id": 5, "mode": "auto-increment", "stop_on_error": false, "total_items": 10, "succeeded": 10, "failed": 0, "status": "COMPLETED", "idempotency_key": null, "finished_at": "2026-06-01T19:31:00.000000Z", "created_at": "2026-06-01T19:30:00.000000Z", "updated_at": "2026-06-01T19:31:00.000000Z" }, { "uuid": "44115613-e378-45e1-a6ea-b93f0319de2a", "company_id": 1, "user_id": 5, "mode": "manual", "stop_on_error": false, "total_items": 2, "succeeded": 0, "failed": 2, "status": "FAILED", "idempotency_key": null, "finished_at": "2026-06-01T21:04:45.000000Z", "created_at": "2026-06-01T21:03:38.000000Z", "updated_at": "2026-06-01T21:04:45.000000Z" } ], "first_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents?page=1", "from": 1, "last_page": 1, "last_page_url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents?page=1", "links": [ { "url": null, "label": "« Previous", "active": false }, { "url": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents?page=1", "label": "1", "active": true }, { "url": null, "label": "Next »", "active": false } ], "next_page_url": null, "path": "https://sandbox-api.matias-api.com/api/ubl2.1/bulk/documents", "per_page": 15, "prev_page_url": null, "to": 2, "total": 2 } ``` --- ## Campos de cada item Campos presentes en todos los items: | Campo | Descripción | |-------|-------------| | `uuid` / `item_id` | Identificador del item (mismo valor). | | `index` | Posición del documento en `documents[]` (desde 0). | | `kind` / `kind_description` | Tipo de documento y su descripción legible. | | `client_reference` | Tu referencia interna. | | `document_uuid` | **UUID del envío del documento: guárdalo.** Es el que usas para consultarlo (`GET /documents/{uuid}`) y, en general, no coincide con el `documents[].uuid` que enviaste. En un item que no llegó a emitirse es el `documents[].uuid` que enviaste, o `null`; en `DUPLICATE_DOCUMENT_UUID`, el del documento que ya existía. | | `shipping_history_id` | Identificador numérico interno del envío. Para consultar el documento usa `document_uuid`. | | `status` / `status_description` | Estado del item y su descripción legible. Ver [Estados del Item](https://docs.matias-api.com/docs/bulk-documents#estados-item). | | `attempts` | Intentos registrados (1 normalmente; hasta 3 con fallos transitorios). | | `error_code` / `error_message` | Código y mensaje del error (mensaje truncado a 500 caracteres). Ver [Catálogo de `error_code`](https://docs.matias-api.com/docs/bulk-documents#error-code). | | `processed_at`, `created_at`, `updated_at` | Fechas en ISO 8601 (UTC). | | `response` | **String** con un JSON de resumen del resultado, o `null` (ver abajo). | Campos adicionales según el estado: | Estado | Campos adicionales | |--------|--------------------| | `SUCCESS`, `REJECTED_BY_DIAN`, `POSSIBLY_SENT` | `document_number`, `cufe` (CUFE/CUDE), `track_id`, `dian_response` (`is_valid`, `status_code`, `status`, `errors`, `notifications`, o `null`) y `warnings`. | | `FAILED`, `REJECTED_BY_DIAN` | `error`: `{ "code": …, "message": … }` con los mismos valores de `error_code` y `error_message`. | | `POSSIBLY_SENT` | `error` con `code` (el `error_code`, o `POSSIBLY_SENT`) y un mensaje fijo que indica no reenviar, y `next_action: "CHECK_STATUS"`. | ### El campo `response` `response` ya **no** trae el contenido crudo del documento (XML, AttachedDocument, QR o PDF en base64). Es un string JSON con este resumen: | Clave | Descripción | |-------|-------------| | `uuid` | UUID del envío del documento (el mismo `document_uuid` del item). | | `document_number` | Número del documento (prefijo + consecutivo). | | `XmlDocumentKey` / `cufe` | CUFE/CUDE del documento. | | `track_id` | Identificador de seguimiento de la DIAN (en habilitación, el ZipKey). | | `is_valid` | `true` si la DIAN lo aceptó, `false` si lo rechazó, `null` si no hay resultado definitivo (también en habilitación, donde solo se confirma la recepción). | | `status_code` / `http_status` | Código HTTP entero de la emisión (por ejemplo, `200`). | | `dian_status_code` | StatusCode de la DIAN (por ejemplo, `"00"` o `"99"`). | | `dian_status` | `ACCEPTED`, `RECEIVED`, `REJECTED` o `POSSIBLY_SENT`. | | `errors` / `notifications` | Rechazos y notificaciones de la DIAN, por separado: un mensaje de la DIAN que contiene «Notificaci» va a `notifications`; los demás, a `errors`. En un `FAILED` por error HTTP, `errors` trae el mensaje del error. | | `warnings` | Avisos del procesamiento (lista de `{ "code", "message" }`). | Si el item termina `FAILED` por un error `4xx` de la emisión (salvo `408` y `429`), `response` **no** es `null`: trae el resumen con `errors: [""]`, `status_code` y `http_status` iguales a ese código HTTP y `dian_status: null`. ```json "{\"uuid\":null,\"document_number\":null,\"XmlDocumentKey\":null,\"cufe\":null,\"track_id\":null,\"is_valid\":null,\"status_code\":422,\"dian_status_code\":null,\"dian_status\":null,\"errors\":[\"Mensaje del error devuelto por la emisión.\"],\"notifications\":[],\"warnings\":[],\"http_status\":422}" ``` `response` es `null` si el item no se procesó, si el fallo ocurrió fuera de la emisión (por ejemplo, una resolución inexistente, `JOB_FAILED` o `STUCK`), si un error transitorio (`408`, `429`, `5xx`) agotó los 3 intentos, o si es un registro antiguo de gran tamaño. En esos casos el detalle está en `error_code` / `error_message`. El `payload` que enviaste nunca se devuelve. Para descargar XML o PDF usa los endpoints del documento con su `document_uuid` o CUFE ([Descarga de Archivos](https://docs.matias-api.com/docs/endpoints/queries-status#descarga-archivos)). Códigos posibles en `warnings`: | Código | Significado | |--------|-------------| | `RECONCILED` | El item se concilió con un envío ya existente de ese mismo documento (no se volvió a emitir). | | `ATTACHMENTS_NOT_LINKED` | El documento se emitió pero los adjuntos no quedaron vinculados. | | `PDF_NOT_GENERATED` | El documento se emitió pero no se generó el PDF. | | `EMAIL_NOT_SENT` | El documento se emitió pero no se envió el correo. | | `ATTACHED_DOCUMENT_NOT_GENERATED` | El documento se emitió pero no se generó el AttachedDocument. | | `ALREADY_AUTHORIZED_RECOVERED` / `ALREADY_AUTHORIZED_OTHER_CONTENT` | La DIAN ya tenía autorizado ese número; el documento se recuperó o la DIAN tiene autorizado un contenido distinto. | --- ## Referencia de Estados ### Estados del Lote (`status`) | Estado | Emoji | Descripción | |--------|:-----:|-------------| | `PENDING` | ⏳ | Aceptado; ningún item ha terminado todavía. | | `PROCESSING` | ⚙️ | Al menos un item terminó y quedan items pendientes. | | `COMPLETED` | ✅ | Todos los items terminaron en `SUCCESS`. | | `PARTIAL` | ⚠️ | Todos terminaron; al menos uno `SUCCESS` y al menos uno `FAILED`, `REJECTED_BY_DIAN`, `POSSIBLY_SENT` o `CANCELLED`. | | `FAILED` | ❌ | Todos terminaron y ninguno quedó en `SUCCESS`. | - Un lote **nunca queda en `CANCELLED`**: con `stop_on_error` termina en `PARTIAL` o `FAILED`, y son sus items en cola los que quedan `CANCELLED`. - Contadores: `succeeded` = items `SUCCESS`; `failed` = items `FAILED` + `REJECTED_BY_DIAN` + `POSSIBLY_SENT`. Los `CANCELLED` no suman en ninguno. - Un lote `FAILED` o `PARTIAL` puede tener documentos que sí llegaron a la DIAN (`REJECTED_BY_DIAN`, `POSSIBLY_SENT`): revisa cada item. ### Estados del Item (`items[].status`) | Estado | Emoji | Descripción | |--------|:-----:|-------------| | `QUEUED` | 📋 | En cola. También mientras espera un reintento (`attempts` mayor que 0, con el error del último intento). | | `PROCESSING` | ⚙️ | Siendo procesado. | | `SUCCESS` | ✅ | La DIAN lo aceptó (en habilitación: lo recibió). | | `FAILED` | ❌ | Falló antes de transmitirse a la DIAN. | | `REJECTED_BY_DIAN` | 🏛️❌ | Transmitido y rechazado por la DIAN. | | `POSSIBLY_SENT` 🆕 | ❓ | La transmisión empezó y no hubo respuesta definitiva de la DIAN. **No lo reenvíes:** consulta su estado. | | `CANCELLED` | 🚫 | Cancelado por `stop_on_error` antes de procesarse. | :::warning `POSSIBLY_SENT`: consultar, nunca reenviar El documento pudo quedar autorizado en la DIAN. Reenviarlo con otro número o con otro contenido puede generar un duplicado. Consulta su estado con el `document_uuid` (`GET /documents/{document_uuid}` o `GET /status`, ver [Consultas y Estados](https://docs.matias-api.com/docs/endpoints/queries-status#consulta-estados)) y decide según el resultado. `POSSIBLY_SENT` cuenta en `failed` del lote y del webhook y dispara `stop_on_error`. - Si el item trae `cufe` (`XmlDocumentKey`), consulta el estado directamente en la DIAN con [`POST /status/document/{CUFE}`](https://docs.matias-api.com/docs/endpoints/queries-status#consulta-estados). Si ese número se está transmitiendo en otra solicitud, el endpoint responde `409` con `code` `DOCUMENT_IN_TRANSMISSION`: consulta de nuevo en unos instantes. - Si `document_uuid` llega `null`, consulta con el UUID anti-duplicados del item (ver [Consultar el documento cuando `document_uuid` es `null`](https://docs.matias-api.com/docs/bulk-documents#document-uuid-null)). ::: ### Cómo se decide el resultado El resultado lo decide **la respuesta de la DIAN, no el código HTTP**: un rechazo de la DIAN llega con HTTP 200. | Respuesta | Estado del item | `dian_response.status` | |-----------|-----------------|------------------------| | `IsValid` = `true` y `StatusCode` = `00` | `SUCCESS` | `ACCEPTED` | | Habilitación: sin `IsValid`, con ZipKey | `SUCCESS` | `RECEIVED` (solo recibido; la validación es asíncrona: consulta `POST /status/zip/{track_id}`) | | `IsValid` presente y no válido | `REJECTED_BY_DIAN` | `REJECTED` (`error_code` = StatusCode DIAN) | | Respuesta sin resultado DIAN reconocible, o error después de iniciar la transmisión | `POSSIBLY_SENT` | `POSSIBLY_SENT` | | Error antes de transmitir | `FAILED` (o reintento si es transitorio) | — | En habilitación la respuesta no trae el número ni el UUID del envío: el item `SUCCESS` llega con `document_number` en `null` y su `document_uuid` es el `documents[].uuid` que enviaste (o `null` si no lo enviaste). Aun así el documento se puede ubicar con su UUID anti-duplicados (ver abajo). #### Consultar el documento cuando `document_uuid` es `null` Todo item se emite con un [UUID anti-duplicados](https://docs.matias-api.com/docs/bulk-documents#uuid-anti-duplicados), que se registra y se vincula al envío **antes** de transmitir. Por eso, aunque `document_uuid` llegue `null` (en habilitación, o en un `POSSIBLY_SENT` cuya respuesta no trajo el UUID), puedes consultar el documento con [`GET /documents/{uuid}`](https://docs.matias-api.com/docs/endpoints/queries-status#busqueda-listado) usando, en este orden: 1. El `documents[].uuid` que enviaste. 2. Si no lo enviaste, el `payload.uuid` (solo si es un UUID válido). 3. Si no enviaste ninguno, el `item_id` del item. `GET /documents/{uuid}` acepta tanto el UUID del envío como el anti-duplicados: | UUID consultado | `dataRecord` | |-----------------|--------------| | UUID del envío (`document_uuid`) | El envío del documento (con `is_valid` y `XmlDocumentKey`). | | UUID anti-duplicados | El vínculo anti-duplicados; el envío viene en `dataRecord.shipping_history`. | | Ninguno de los dos existe | `200` con `dataRecord: null`. | Enviar siempre `documents[].uuid` simplifica esta búsqueda: es un valor que ya tienes en tu sistema. ### Catálogo de `error_code` | Estado | `error_code` | Significado | Qué hacer | |--------|--------------|-------------|-----------| | `FAILED` | Código HTTP como string: `"400"`, `"404"`, `"409"`, `"422"`… | Error del documento o de los datos (por ejemplo, `"404"` cuando no hay resolución activa). | Corregir y reenviar. El UUID anti-duplicados queda libre. | | `FAILED` | `"408"`, `"429"`, `"5xx"` | Error transitorio que persistió tras agotar los 3 intentos. | Reenviar. | | `FAILED` | `DUPLICATE_DOCUMENT_UUID` | El UUID ya se usó en otro documento de la empresa; no se emitió de nuevo. | Consultar el documento al que apunta `document_uuid`. Si es otro documento, enviar el tuyo con un UUID nuevo. | | `FAILED` | `JOB_FAILED` | El procesamiento del item no terminó y no hay envío del documento. | Reenviar sin riesgo: no existe el vínculo anti-duplicados, así que el documento no se transmitió. | | `FAILED` | `STUCK` | El item quedó detenido sin transmisión y se cerró en la conciliación (ver [Items detenidos](https://docs.matias-api.com/docs/bulk-documents#items-detenidos)). | Reenviar sin riesgo, por la misma razón que `JOB_FAILED`. | | `FAILED` | Otro identificador técnico (código o tipo de la excepción) | Error interno sin código HTTP asociado. | Reenviar; si se repite, reportarlo a soporte con el `item_id`. | | `REJECTED_BY_DIAN` | StatusCode de la DIAN (por ejemplo, `"99"`) o `DIAN` | Rechazo de la DIAN. `DIAN` aparece cuando la respuesta no trae StatusCode o cuando el item se concilió con un envío rechazado. | Corregir según `dian_response.errors`. | | `POSSIBLY_SENT` | `DOCUMENT_POSSIBLY_SENT` | La transmisión empezó y no hubo respuesta definitiva. | **No reenviar.** Consultar el estado. | | `POSSIBLY_SENT` | `DOCUMENT_IN_TRANSMISSION` | Otro proceso estaba transmitiendo ese mismo número. | **No reenviar.** Consultar el estado. | | `POSSIBLY_SENT` | `DOCUMENT_IN_VALIDATION` | La DIAN aún valida un envío anterior del mismo número (StatusCode 98). | **No reenviar.** Consultar el estado. | :::note Cambio reciente Los casos `DOCUMENT_IN_TRANSMISSION` y `DOCUMENT_IN_VALIDATION` (un `409` del pipeline unitario) antes terminaban `FAILED`. Ahora terminan `POSSIBLY_SENT`, porque ese número puede quedar autorizado en la DIAN. ::: ### Reintentos automáticos - Solo se reintentan los resultados transitorios **`408`, `429` y `5xx`**: hasta **3 intentos**, con 30 segundos de espera entre intentos. Mientras espera, el item vuelve a `QUEUED` con `attempts` mayor que 0 y el `error_code` / `error_message` del último intento. - Los demás errores `4xx` no se reintentan: el item queda `FAILED` de inmediato. - Un documento cuya transmisión ya empezó nunca se reemite. Si la API no pudo confirmar en la DIAN el estado de un envío anterior (`503`), en el siguiente intento el item se concilia con ese envío: normalmente termina `POSSIBLY_SENT` (`DOCUMENT_POSSIBLY_SENT`, warning `RECONCILED`); termina `SUCCESS` si el envío ya quedó válido, o `REJECTED_BY_DIAN` (`error_code` `DIAN`) si consta como rechazado. ### Items detenidos y conciliación automática Si el procesamiento de un item muere (por ejemplo, se detiene el proceso que lo atendía), la API lo resuelve sola. La revisión corre **cada 10 minutos**: - Un item que lleva **más de 30 minutos** en `PROCESSING` sin cambios se concilia con lo que conste de su envío. Termina `SUCCESS`, `REJECTED_BY_DIAN` (`error_code` `DIAN`) o `POSSIBLY_SENT`, con el warning `RECONCILED`, o `FAILED` con `STUCK` si nunca llegó a transmitirse. - Un item que sigue en `QUEUED` **más de 6 horas** se vuelve a despachar. No se emite dos veces: el item se reclama de forma atómica. - Nunca se reemite un documento que ya se transmitió. En la práctica, cuando falla el procesamiento, un lote puede quedarse en `PROCESSING` **unos 40 minutos** antes de cerrarse. Si haces polling, no lo des por perdido antes de ese tiempo. --- ## Idempotencia El endpoint `POST` soporta idempotencia mediante el header **`Idempotency-Key`** (un UUID v4, máximo 36 caracteres). | Escenario | Resultado | |-----------|-----------| | Primera vez con `Idempotency-Key` | Se crea el lote normalmente (`201`). | | Mismo key + mismo body | Se retorna el lote original con su estado actual, sin reprocesar (`201`; `estimated_completion_seconds` es `0` si ya terminó). | | Mismo key + body distinto | Error `409 Conflict`. | | Key de más de 36 caracteres | Error `422`. | | Sin header | Cada request crea un lote nuevo. | - La comparación se hace sobre el **body exacto** (huella SHA-256 de los bytes recibidos): cualquier cambio, incluso de espacios u orden de claves, cuenta como body distinto. Reenvía exactamente los mismos bytes. - Ante un `429` o un error de red, reintenta con el **mismo** `Idempotency-Key` y el mismo body: si el lote ya se había creado, recibes el original en lugar de un duplicado. :::warning Vigencia El `Idempotency-Key` tiene una validez de **24 horas** desde la creación del lote. Después de ese período, la key se libera y el mismo valor crea un lote nuevo. ::: --- ## Modos de Envío ### `auto-increment` El sistema asigna automáticamente el número de documento a partir de la resolución. - La resolución se busca por `resolution_number` + `prefix` (del `payload`, o de `default_resolution_number` / `default_prefix` del lote) y debe estar **activa**. Verifica además que esté vigente: en bulk no se valida su rango de fechas. - Si no existe una resolución activa con esos datos, el item termina `FAILED` con `error_code` `"404"` y el mensaje `No se encontró una resolución de facturación activa (resolution_number: …, prefix: …).`. - Un `document_number` enviado en el `payload` **se sobrescribe** con el consecutivo asignado. - **Consecutivos en fallos:** - Si el item falla **antes de transmitirse**, la reserva de su número se libera. El siguiente número se calcula como el mayor reservado + 1, así que el número liberado solo se reutiliza si seguía siendo el último de la resolución; si ya se reservaron números posteriores, queda un hueco en la numeración. - Si la transmisión ya empezó (`REJECTED_BY_DIAN`, `POSSIBLY_SENT`), el número queda asociado a ese documento. ### `manual` Cada documento debe traer su propio `document_number` en el `payload`. El sistema **no** asigna consecutivos. --- ## `stop_on_error` | Valor | Comportamiento | |:-----:|----------------| | `false` | Todos los documentos se procesan independientemente. Los fallos no afectan a los demás. | | `true` | El primer documento que termine en un estado distinto de `SUCCESS` (`FAILED`, `REJECTED_BY_DIAN` o `POSSIBLY_SENT`) cancela los items que siguen en cola (`CANCELLED`). Los items que ya se estaban procesando terminan normalmente. El lote termina en `PARTIAL` o `FAILED`. | --- ## Códigos de Error HTTP | HTTP | Causa | Descripción | |:----:|-------|-------------| | `201` | ✅ Éxito | Lote aceptado (también en un replay idempotente). | | `402` | Membresía o cuota | Membresía sin cupo o inactiva, o cuota insuficiente para el lote completo. | | `404` | Lote no encontrado | En las consultas: el lote no existe, ya se purgó o pertenece a otra empresa. | | `409` | Conflicto idempotencia | `Idempotency-Key` reutilizado con un body distinto. | | `413` | Lote muy grande | El número de documentos excede el máximo de tu plan. Se valida antes que el esquema. | | `422` | Validación | El request no cumple el esquema, el `Idempotency-Key` supera 36 caracteres, o `documents` llega vacío. | | `429` | Límite de frecuencia o de carga | Lotes por minuto excedidos, demasiados documentos en proceso u otro lote de la empresa en admisión. | ### Orden de los controles en `POST /bulk/documents` La API aplica los controles en este orden y responde con el primero que falla: 1. `429`: lotes por minuto (por usuario y por empresa). El intento **se cuenta aunque la petición falle después**. 2. `402`: membresía sin cupo o inactiva. 3. `422` `Compañía o lote inválido.` si `documents` está vacío o no es un array; luego `413` por el tope del plan y `402` por cuota insuficiente (con `requested`). 4. `422`: esquema del request. 5. `422`: `Idempotency-Key` de más de 36 caracteres. 6. `429`: otro lote de la empresa en admisión (la API espera hasta 5 segundos antes de responder). 7. Replay idempotente: `201` con el lote original, o `409` si el body difiere. 8. `429`: demasiados documentos en proceso. 9. `402`: cuota revalidada (solo `message`). :::warning Un reintento idempotente también pasa por los controles Reintentar con el mismo `Idempotency-Key` consume cupo de lotes por minuto y pasa por membresía, tope del plan, cuota y esquema **antes** de devolver el lote original. Por ejemplo, si la cuota se agotó entretanto, recibes `402` aunque el lote ya exista. Además, la cuota disponible descuenta los documentos aún pendientes de lotes en curso, incluido el propio lote original: mientras ese lote se procesa, el replay puede recibir `402` si lo que queda de cuota no alcanza para volver a cubrir el lote completo. Guarda el `batch_id` del primer `201` para no depender del replay. ::: **Error 413** ```json { "success": false, "message": "El lote excede el máximo permitido para tu suscripción.", "max_items": 50, "received": 75, "upgrade_url": "https://sandbox-api.matias-api.com/upgrade" } ``` Topes por plan: FREE 5, Pago 50, Ilimitado 200. **Error 402** La API responde `402` en tres formas: ```json { "success": false, "message": "Límite de documentos de la membresía alcanzado o membresía inactiva." } ``` ```json { "success": false, "message": "Cuota insuficiente para procesar el lote completo.", "requested": 25 } ``` Al revalidar la cuota justo antes de crear el lote (descontando los documentos pendientes de lotes anteriores), la respuesta trae solo `message`: ```json { "message": "Cuota insuficiente para procesar el lote completo." } ``` **Error 422** Errores de esquema, con el formato estándar de validación (`message` con el primer error y `errors` por campo; sin `success`): ```json { "message": "El campo mode es obligatorio (auto-increment o manual). (and 1 more error)", "errors": { "mode": ["El campo mode es obligatorio (auto-increment o manual)."], "documents.0.kind": [ "Tipo de documento no válido. Tipos permitidos: invoice, credit_note, debit_note, support_document, adjustment_note, pos_document" ] } } ``` Otros mensajes de esquema: | Regla | Mensaje | |-------|---------| | `mode` con un valor distinto de `auto-increment` o `manual` | `El campo mode debe ser auto-increment o manual.` (el de «obligatorio» solo sale cuando falta) | | `payload` que no es un objeto JSON | `El payload de cada documento debe ser un objeto JSON.` | | `uuid` no válido / repetido en el lote | `El UUID del documento debe ser un UUID v4 válido.` / `El UUID del documento está repetido dentro del lote.` | | Más de 4 adjuntos | `Cada documento admite máximo 4 adjuntos.` | | Adjunto sin `filename` / sin `content` | `Cada adjunto debe incluir filename.` / `Cada adjunto debe incluir content (base64).` | | `client_reference` de más de 64 caracteres | `La referencia del cliente no puede exceder 64 caracteres.` | | Más de 200 documentos | `El lote no puede contener más de 200 documentos.` (solo cuando el tope del plan no lo frena antes con `413`) | | `content_type` de más de 100, `filename` de más de 255, `default_resolution_number` de más de 50, `default_prefix` de más de 10, o `stop_on_error` no booleano | Mensaje estándar de validación (longitud máxima o tipo). | `Idempotency-Key` de más de 36 caracteres (solo `message`): ```json { "message": "Idempotency-Key no puede superar 36 caracteres (use un UUID v4)." } ``` `documents` vacío o ausente (se valida antes que el esquema): ```json { "success": false, "message": "Compañía o lote inválido." } ``` Según el entorno, un `documents` vacío también puede llegar como error de esquema (`Debe incluir al menos un documento en el lote.`). **Error 409** ```json { "message": "Idempotency-Key reutilizado con payload distinto." } ``` **Error 429** **Lotes por minuto.** En el plan FREE la respuesta **no trae `Retry-After`** ni `X-RateLimit-Reset`: espera **60 segundos** (la ventana es de 1 minuto). Los `X-RateLimit-Limit` / `X-RateLimit-Remaining` que acompañen este `429` corresponden al límite general de la API, no al de lotes por minuto. ```json { "success": false, "message": "Plan FREE: máximo 1 lote bulk por minuto. Actualice su plan.", "upgrade_url": "https://sandbox-api.matias-api.com/upgrade" } ``` En los planes de pago e ilimitado, la respuesta estándar de límite de frecuencia, con los headers `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` y `X-RateLimit-Reset`. Las respuestas correctas también traen `X-RateLimit-Limit` y `X-RateLimit-Remaining`, útiles para dosificar los envíos. ```json { "message": "Too Many Attempts." } ``` **Demasiados documentos en proceso.** `Retry-After` estima en segundos cuánto tarda la cola en drenar los documentos que sobran (excedente × ~6 s ÷ procesos que atienden la cola, mínimo 1). ```json { "message": "La empresa ya tiene 980 documentos en proceso y el máximo simultáneo es 1000. Reintente más tarde." } ``` **Otro lote de la misma empresa en admisión.** La API espera hasta 5 segundos a que termine la admisión del otro lote y, si no termina, responde con `Retry-After: 5`. ```json { "message": "Hay otro lote de esta empresa en admisión. Reintente en unos segundos." } ``` Espera los segundos de `Retry-After` (60 en el plan FREE) y reintenta con el mismo `Idempotency-Key`. --- ## Webhook Cuando un lote finaliza (todos sus items en estado terminal), se dispara **una sola vez** el evento **`bulk.batch.completed`**. Llega con el sobre estándar de los webhooks: ```json { "id": "evt_aB3dE5fG7hJ9kL1mN3pQ5rS7", "type": "bulk.batch.completed", "created_at": "2026-06-01T19:31:01+00:00", "api_version": "3.0", "data": { "object": "document_batch", "batch_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "PARTIAL", "total_items": 10, "succeeded": 9, "failed": 1, "finished_at": "2026-06-01T19:31:00+00:00" } } ``` - La clave del evento es `type` (no `event`). - `failed` incluye los items `REJECTED_BY_DIAN` y `POSSIBLY_SENT`. - No trae datos de los documentos: consulta el lote (`GET /bulk/documents/{batch_id}`) para el detalle de cada item. - Para recibirlo, suscribe un webhook al evento `bulk.batch.completed`. Es un evento válido aunque `GET /webhooks/events` no lo liste. - Se firma y se reintenta igual que el resto de eventos: ver [Seguridad HMAC](https://docs.matias-api.com/docs/endpoints/webhooks#seguridad-hmac) y [Reintentos automáticos](https://docs.matias-api.com/docs/endpoints/webhooks#reintentos). --- ## Guía Rápida de Integración ### Paso 1: Enviar el lote ```bash curl -X POST "{{url}}/bulk/documents?client_uuid={{client_uuid}}" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -d '{ "mode": "auto-increment", "default_resolution_number": "18764002566734", "default_prefix": "SETT", "documents": [ { "kind": "invoice", "uuid": "6f1c2b9e-3d4a-4e8b-9c71-2a5d8e0f4b13", "client_reference": "MI-REF-001", "payload": { "type_document_id": 7, "...": "payload completo de factura" } }, { "kind": "invoice", "uuid": "0d9e8f7a-6b5c-4d3e-8f2a-1b0c9d8e7f6a", "client_reference": "MI-REF-002", "payload": { "type_document_id": 7, "...": "payload completo de factura" } } ] }' ``` > **Respuesta:** `201 Created` con `batch_id`. Omite `?client_uuid=` si emites para tu propia empresa. ### Paso 2: Polling del estado Espera unos segundos (el `estimated_completion_seconds` te da una estimación) y luego consulta: ```bash curl -X GET "{{url}}/bulk/documents/{batch_id}?client_uuid={{client_uuid}}" \ -H "Authorization: Bearer {token}" \ -H "Accept: application/json" ``` > Repite hasta que `status` sea `COMPLETED`, `PARTIAL` o `FAILED`, o suscríbete al [webhook](https://docs.matias-api.com/docs/bulk-documents#webhook) `bulk.batch.completed`. ### Paso 3: Guardar los `document_uuid` Guarda el `document_uuid` de **cada item que lo tenga**, no solo de los `SUCCESS`: los `REJECTED_BY_DIAN` y `POSSIBLY_SENT` también corresponden a documentos que llegaron a la DIAN. Este UUID es la referencia permanente del documento. ```javascript // Ejemplo: extraer UUIDs del response const batch = await response.json(); const documents = batch.items .filter(item => item.document_uuid) .map(item => ({ document_uuid: item.document_uuid, // ← GUARDAR en su BD client_reference: item.client_reference, // ← Su referencia interna status: item.status, document_number: item.document_number ?? null, cufe: item.cufe ?? null })); // Persistir en su sistema para consultas futuras await saveToDatabase(documents); // Documentos sin respuesta definitiva: consultar su estado, nunca reenviar const toCheck = batch.items.filter(item => item.next_action === 'CHECK_STATUS'); ``` ### Paso 4: Revisar los que no terminaron en `SUCCESS` ```bash curl -X GET "{{url}}/bulk/documents/{batch_id}/items?status=FAILED&client_uuid={{client_uuid}}" \ -H "Authorization: Bearer {token}" \ -H "Accept: application/json" ``` Repite con `status=REJECTED_BY_DIAN` y `status=POSSIBLY_SENT`: - **`FAILED`:** corrige el documento según `error_code` / `error_message` y reenvíalo en un lote nuevo. Si no llegó a transmitirse, puedes usar el mismo `uuid`. - **`REJECTED_BY_DIAN`:** revisa `dian_response.errors` y corrige el documento. - **`POSSIBLY_SENT`:** consulta el estado con el `document_uuid` antes de cualquier acción; **no lo reenvíes**. --- ## Retención de Datos El lote es solo un mecanismo de carga masiva: cada documento se emite y se conserva individualmente, igual que en el endpoint unitario. Por eso los datos del lote se limpian pronto. | Qué | Cuándo | Efecto | |-----|:------:|--------| | `payload` de items `SUCCESS` | **1 día** después de procesado | Se vacía. El resumen del item (`document_uuid`, `document_number`, `cufe`, `dian_response`, `error`…) se conserva. | | `payload` de items `FAILED`, `REJECTED_BY_DIAN` y `CANCELLED` | **2 días** después de procesado | Se vacía. El resumen del item se conserva. | | Items `POSSIBLY_SENT` | — | No se limpian antes de la purga del lote. | | Lote completo (lote + items) | **7 días** desde la creación del lote | Se elimina, solo si el lote terminó (`COMPLETED`, `PARTIAL` o `FAILED`). | - El `payload` nunca se expone por la API; su limpieza no cambia lo que ves en las consultas. - Hasta la purga del lote, sus items siguen disponibles en `GET /bulk/documents/{batch_id}` y en `/items`. Después, el endpoint responde `404`: consulta los documentos por su `document_uuid`, o por número y prefijo con [Búsqueda y Listado](https://docs.matias-api.com/docs/endpoints/queries-status#busqueda-listado). --- ## Notas Técnicas :::info Para desarrolladores - Cada documento se procesa de forma independiente, con hasta **3 intentos** (30 segundos entre intentos) solo para fallos transitorios (`408`, `429`, `5xx`). - `estimated_completion_seconds` se calcula con la cola actual, a razón de **~6 segundos por documento** (varía según la latencia con la DIAN). - El webhook `bulk.batch.completed` se dispara una sola vez, cuando **todos** los items alcanzaron un estado terminal. - El lote completo se elimina a los **7 días** de su creación: guarda los `document_uuid` antes. - Si el procesamiento de un item muere, la API lo concilia sola en unos 40 minutos y nunca reemite un documento ya transmitido. Ver [Items detenidos y conciliación automática](https://docs.matias-api.com/docs/bulk-documents#items-detenidos). ::: :::danger Límite de tamaño Mantén el body del request por debajo de **5 MB**. Ese límite lo aplica el servidor web, no la validación de la API: un body mayor puede rechazarse sin el formato JSON de esta página. Para lotes grandes, divide en múltiples requests. ::: --- ## Cambios y compatibilidad 🆕 Si ya consumías el envío masivo, revisa estos cambios de la versión actual. ### Cambios incompatibles | Cambio | Antes | Ahora | Qué debes hacer | |--------|-------|-------|-----------------| | `items[].response` | JSON con la clave `raw` (XML, AttachedDocument, QR y PDF en base64). | String JSON con el resumen: `uuid`, `document_number`, `XmlDocumentKey`/`cufe`, `track_id`, `is_valid`, `status_code`, `dian_status_code`, `dian_status`, `errors`, `notifications`, `warnings`, `http_status`. La clave `raw` ya no existe: los registros antiguos se normalizan al resumen y `raw` nunca se vuelve a exponer. | No leas `response.raw`. Descarga XML y PDF con el `document_uuid` ([Descarga de Archivos](https://docs.matias-api.com/docs/endpoints/queries-status#descarga-archivos)). Ver [El campo `response`](https://docs.matias-api.com/docs/bulk-documents#campo-response). | | `response.status_code` y `response.dian_response` | `status_code` era el código HTTP de la emisión; el detalle de la DIAN venía en `response.dian_response` y en `raw`. | `status_code` sigue siendo el **código HTTP** entero (igual a `http_status`). `response` ya no trae `dian_response`: el StatusCode de la DIAN va en `dian_status_code` y, a nivel de item, en `dian_response.status_code`. | No busques el código de la DIAN en `status_code`: usa `dian_status_code` o `items[].dian_response`. | | `SUCCESS` frente a `REJECTED_BY_DIAN` | Lo decidía el código HTTP. | Lo decide la respuesta de la DIAN (`IsValid` = `true` y `StatusCode` = `00`). En habilitación, `SUCCESS` significa recibido (ZipKey). | Usa `dian_response` para el detalle. En habilitación, consulta el resultado con el `track_id`. Ver [Cómo se decide el resultado](https://docs.matias-api.com/docs/bulk-documents#decision-resultado). | | Estado `POSSIBLY_SENT` | No existía. | Estado nuevo: cuenta en `failed`, dispara `stop_on_error` y el documento pudo llegar a la DIAN. | **No lo reenvíes:** consulta su estado. Ver [Estados del Item](https://docs.matias-api.com/docs/bulk-documents#estados-item). | | `documents[].uuid` | Opcional, sin efecto anti-duplicados. | UUID anti-duplicados: debe ser un UUID válido y distinto dentro del lote. Si ya se usó en otro documento, el item termina `FAILED` con `DUPLICATE_DOCUMENT_UUID`. | No reutilices el UUID de otro documento. Ver [UUID anti-duplicados](https://docs.matias-api.com/docs/bulk-documents#uuid-anti-duplicados). | | `items[].document_uuid` | UUID del envío solo en los `SUCCESS`; en los demás, el `documents[].uuid` que enviaste (o `null`). | El UUID del **envío** en todo item que tenga envío (también `REJECTED_BY_DIAN` y `POSSIBLY_SENT`); en `DUPLICATE_DOCUMENT_UUID`, el del documento existente; si no hay envío, el `documents[].uuid` enviado o `null`. | Guarda el `document_uuid` que devuelve la API. Ver [Campos de cada item](https://docs.matias-api.com/docs/bulk-documents#campos-item). | | Nuevos `429` con `Retry-After` | Solo el de lotes por minuto. | Además, demasiados documentos en proceso y otro lote de la empresa en admisión. El límite de lotes por minuto se comparte por empresa. | Respeta `Retry-After` y reintenta con el mismo `Idempotency-Key`. | | Nuevos `422` | — | Tope técnico de 200 documentos, UUID repetido en el lote, reglas de adjuntos e `Idempotency-Key` de más de 36 caracteres. | Ajusta el armado del lote. Ver [Códigos de Error HTTP](https://docs.matias-api.com/docs/bulk-documents#errores). | | `error_code` | Códigos sin catálogo. | Catálogo documentado: código HTTP como string, `DUPLICATE_DOCUMENT_UUID`, `JOB_FAILED`, `STUCK`, códigos de la DIAN, los de `POSSIBLY_SENT` y, para errores internos, un identificador técnico. | Actualiza tu mapeo de errores. Ver [Catálogo de `error_code`](https://docs.matias-api.com/docs/bulk-documents#error-code). | ### Cambios compatibles - Ahora documentado: el `413` incluye `upgrade_url`. - Los items exponen `item_id`, `kind_description`, `status_description`, `document_number`, `cufe`, `track_id`, `dian_response`, `warnings`, `error` y `next_action`. - El resumen de `response` se conserva hasta la purga del lote. - `per_page` se limita a 200 en items y a 100 en lotes. --- # 📋 Campos de todos los documentos electrónicos **📖 Referencia Completa de Campos** En esta sección se describen **todos los campos** que se deben considerar para la generación de factura electrónica, nota de crédito y nota de débito, documento soporte y documento equivalente, con sus respectivas notas de ajuste. 📄 **Factura Electrónica** Tipos 7, 8, 9, 10 📝 **Notas de Ajuste** Crédito (5) / Débito (4) 🏪 **POS Electrónico** Tipo 20 📑 **Documento Soporte** Tipo 11 --- ## 🎯 Referencia Rápida de Tipos de Documento #### ⚠️ IMPORTANTE: IDs del API vs Códigos DIAN Los valores en la columna **ID (API)** son los que debes usar en `type_document_id`. El **Code (DIAN)** es solo para referencia con la normativa DIAN. **🔑 REGLA DE ORO:** En el API **SIEMPRE** usas el `ID de la DB`, **NUNCA** el code DIAN. | ID (API) | Code (DIAN) | Tipo | Descripción | Normativa | Notas | | -------- | ----------- | ------------------------------- | --------------------------------------- | --------- | --------------------------------------------------------------------- | | **7** | 01 | Factura de Venta | Documento estándar de venta | Res. 165 | `type_document_id: 7` | | **8** | 02 | Factura de Exportación | Factura para operaciones con exterior | Res. 165 | `type_document_id: 8` | | **9** | 03 | Factura de Contingencia Tipo 03 | Emitida cuando falla conexión DIAN | Res. 165 | **`type_document_id: 9`** ⚠️ Requiere `additional_document_reference` | | **10** | 04 | Factura de Contingencia Tipo 04 | Protocolo alternativo especial | Res. 165 | **`type_document_id: 10`** - Opcional `additional_document_reference` | | **11** | 05 | Documento Soporte | Para servicios y operaciones especiales | Res. 165 | `type_document_id: 11` | | **20** | 20 | Documento Equivalente POS | Factura de Punto de Venta | Res. 165 | **`type_document_id: 20`** ⚠️ Requiere `point_of_sale` | | **5** | 91 | Nota Crédito | Devolución o descuento | Res. 165 | `type_document_id: 5` ⚠️ Genera CUDE | | **4** | 92 | Nota Débito | Ajuste por aumento | Res. 165 | `type_document_id: 4` ⚠️ Genera CUDE | --- ## ✅ Compatibilidad de Campos por Tipo **📊 Matriz de Compatibilidad:** Verifica qué campos son obligatorios (🔴), opcionales (🟢) o condicionales (🟡) según el tipo de documento. | Campo | Factura (1,2) | Contingencia (3,4) | Documento Soporte (7) | POS (9) | Nota Crédito (91) | Nota Débito (92) | | ------------------------------- | :-----------: | :----------------: | :-------------------: | :-----: | :---------------: | :--------------: | | `resolution_number` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `prefix` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `date` | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | | `document_number` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `operation_type_id` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `type_document_id` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `payments` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `customer` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `legal_monetary_totals` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `lines` | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | | `additional_document_reference` | 🟢 | 🔴 | 🟢 | 🟢 | 🟢 | 🟢 | | `point_of_sale` | 🟢 | 🟢 | 🟢 | 🔴 | 🟢 | 🟢 | | `billing_reference` | 🟢 | 🟢 | 🟢 | 🟢 | 🔴 | 🔴 | | `order_reference` | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🔴 **Obligatorio** 🟢 **Opcional** 🟡 **Condicional** --- ## 🔧 Referencia de Campos :::info Guía de Uso A continuación se describe el uso detallado de cada campo del request body. Los campos están agrupados por función para facilitar la navegación. ::: ### `resolution_number` 🔴 Número de resolución de facturación asignado por la DIAN. Debe coincidir exactamente con el valor configurado en el portal web del proveedor tecnológico. _Este campo es obligatorio_ para todos los documentos y debe ser un string. ### `prefix` 🟡 Prefijo asociado a la resolución de facturación. Identifica la serie o rango del documento (ej. `"FEV"`, `"NC"`, `"DS"`). _Este campo es condicional_: obligatorio cuando la resolución tiene prefijo configurado. Debe ser un string. ### `date` 🟢 Fecha de emisión del documento. _Este campo es opcional_. Si no se envía, la API asigna la **fecha actual del servidor**. Debe ser un string en formato **`YYYY-MM-DD`** (ej. `"2025-05-25"`). ### `expiration_date` 🟢 Fecha de vencimiento del documento. _Este campo es opcional_ y debe ser un string en formato **`YYYY-MM-DD`**. Se utiliza principalmente en documentos equivalentes electrónicos y debe corresponder con las fechas negociadas o acordadas de pago. ### `time` 🟢 Hora de emisión del documento. _Este campo es opcional_. Si no se envía, la API asigna la **hora actual del servidor**. Debe ser un string en formato **`HH:mm:ss`** (ej. `"14:30:00"`). ### `notes` 🟢 Notas u observaciones adicionales del documento. Se imprime en la representación gráfica (PDF). _Este campo es opcional_ y debe ser un string. Acepta texto libre de cualquier longitud. ### `document_number` 🔴 Número consecutivo del documento **sin prefijo**. _Este campo es obligatorio_ para todos los documentos. Debe ser un número entero (ej. `990000001`). Este número debe estar dentro del rango autorizado por la resolución DIAN vigente. :::note Numeración automática En los endpoints `/auto-increment/*` **no** se envía `document_number`: la plataforma lo asigna. Ver [Numeración Automática](https://docs.matias-api.com/docs/endpoints/autoincrement.md). ::: ### `uuid` 🟢 **NEW** Identificador del documento **de tu lado** (anti-duplicados). _Este campo es opcional_ y va en la raíz del body. Se recomienda enviar un UUID (ej. `"3d6f1a8e-2b4c-4f7d-9e0a-6c1b5d8f2a47"`): la emisión no valida su formato, pero la consulta `GET /documents/{uuid}` sí exige un UUID válido. El `uuid` se registra **antes** de transmitir el documento a la DIAN, de modo que un reintento con el mismo `uuid` no emite un documento distinto: | Situación | Resultado | |---|---| | Reintentas el **mismo número** con el **mismo** `uuid` | Se procesa normalmente (no choca). | | El `uuid` ya pertenece a **otro** documento de la empresa (`POST /invoice`, `/notes/*`, `/ds/*` y los `PATCH /auto-increment/*`) | HTTP `409`: `El uuid proporcionado ya se usó en otro documento de la empresa. No se emitió de nuevo: consulte ese documento o envíe un uuid nuevo.` El consecutivo no se consume. | | El `uuid` ya fue usado y lo envías en `POST /auto-increment/*` | HTTP `400` con el estado del envío original. Ver [UUID ya usado](https://docs.matias-api.com/docs/endpoints/autoincrement#uuid-ya-usado). | | La emisión falla antes de transmitir a la DIAN | El `uuid` se libera y puedes volver a usarlo. | Puedes consultar el documento con `GET /documents/{uuid}` usando tu `uuid` o el `uuid` del envío que devuelve la respuesta de emisión. - #### Ejemplo ```json "uuid": "3d6f1a8e-2b4c-4f7d-9e0a-6c1b5d8f2a47" ``` ### `operation_type_id` 🔴 Se refiere al tipo de operación que afecta al documento. _Este campo es obligatorio_ para todos los documentos y debe ser un entero que corresponda al **ID** de la tabla `operation_types` del API. :::warning IMPORTANTE En el API siempre se envía el **ID** (columna izquierda), **NUNCA** el code DIAN. El valor más común es `1` (Estándar). ::: **📄 Factura Electrónica y Documento Soporte** | ID (API) | Code DIAN | Tipo de Operación | Uso | |:--------:|:---------:|-------------------|-----| | **1** | 10 | Estándar | ✅ Valor por defecto para la mayoría de facturas | | **2** | 09 | Servicios AIU | Administración, Imprevistos y Utilidad | | **3** | 11 | Mandatos | Operaciones de mandato ([ver `lines->mandate`](https://docs.matias-api.com/docs/billing-fields#lines-mandate)) | | **4** | 12 | Transporte | Sector transporte | | **5** | 14 | Notarios | Sector notarial | | **6** | 15 | Compra Divisas | Operaciones de compra de divisas | | **7** | 16 | Venta Divisas | Operaciones de venta de divisas | **📑 Documento Soporte (Residente / No Residente)** | ID (API) | Code DIAN | Tipo de Operación | Uso | |:--------:|:---------:|-------------------|-----| | **9** | 10 | Residente | Proveedor residente en Colombia | | **10** | 11 | No Residente | Proveedor no residente — Requiere [`payment_exchange_rate`](https://docs.matias-api.com/docs/billing-fields#payment_exchange_rate-) | **📝 Notas de Ajuste (Crédito / Débito)** | ID (API) | Code DIAN | Tipo de Operación | Uso | |:--------:|:---------:|-------------------|-----| | **11** | 20 | Nota ajuste Doc. Equivalente POS | Ajuste al documento equivalente POS | | **12** | 20 | Nota Crédito → Factura Electrónica | Referencia una factura electrónica existente | | **13** | 24 | Nota Ajuste → FE Aceptada | Para facturas electrónicas de venta aceptadas | | **14** | 30 | Nota Débito → Factura Electrónica | Referencia una factura electrónica existente | | **15** | 22 | Nota Crédito sin referencia FE | Sin referencia a una factura electrónica | | **16** | 32 | Nota Débito sin referencia FE | Sin referencia a una factura electrónica | - #### Ejemplo ```json "operation_type_id": 1 ``` ### `type_document_id` 🔴 Se refiere al tipo de documento que se está enviando a la DIAN. _Este campo es obligatorio_ para todos los documentos y debe ser un **entero que corresponda al `id` de la tabla `accounting_documents` del API** (NO el `code` DIAN). :::danger CRÍTICO En el API **SIEMPRE** se usa el **`id`** de la base de datos (columna izquierda). **NUNCA** el `code` DIAN. Confundir estos valores es el error más común de integración. ::: :::info Columnas de referencia - **`id`** → Valor que se envía en `type_document_id` - **`code`** → Código DIAN (solo referencia normativa) - **`cufe_algorithm`** → Algoritmo de hash que genera la DIAN para el documento - **`apply_notes`** → `1` si el documento acepta notas de crédito/débito ::: **📄 Facturas Electrónicas** | `id` | `code` | Documento | Algoritmo | Notas | Requisitos especiales | |:----:|:------:|-----------|:---------:|:-----:|----------------------| | **7** | 01 | Factura electrónica | CUFE-SHA384 | ✅ | — | | **8** | 02 | Factura de Exportación | CUFE-SHA384 | ✅ | Requiere [`payment_exchange_rate`](https://docs.matias-api.com/docs/billing-fields#payment_exchange_rate-) | | **9** | 03 | Factura por Contingencia Facturador | CUDE-SHA384 | ✅ | ⚠️ Requiere [`additional_document_reference`](https://docs.matias-api.com/docs/billing-fields#additional_document_reference-referencia-a-documento-adicional) | | **10** | 04 | Factura por Contingencia DIAN | CUFE-SHA384 | ✅ | — | **📑 Documento Soporte** | `id` | `code` | Documento | Algoritmo | Notas | Requisitos especiales | |:----:|:------:|-----------|:---------:|:-----:|----------------------| | **11** | 05 | Documento de Soporte | CUDS-SHA384 | ✅ | Requiere `invoice_period` en cada línea | | **15** | 95 | Nota de ajuste documento soporte | CUDS-SHA384 | ❌ | Requiere [`billing_reference`](https://docs.matias-api.com/docs/billing-fields#billing_reference-) | **🏪 Documento Equivalente POS** | `id` | `code` | Documento | Algoritmo | Notas | Requisitos especiales | |:----:|:------:|-----------|:---------:|:-----:|----------------------| | **20** | 20 | P.O.S Electrónico | CUDE-SHA384 | ✅ | ⚠️ Requiere [`point_of_sale`](https://docs.matias-api.com/docs/billing-fields#point_of_sale-) y [`software_manufacturer`](https://docs.matias-api.com/docs/billing-fields#software_manufacturer-) | | **93** | 93 | Nota débito P.O.S Electrónico | CUDE-SHA384 | ❌ | Requiere [`billing_reference`](https://docs.matias-api.com/docs/billing-fields#billing_reference-) | | **94** | 94 | Nota crédito P.O.S Electrónico | CUDE-SHA384 | ❌ | Requiere [`billing_reference`](https://docs.matias-api.com/docs/billing-fields#billing_reference-) con `scheme_name` | **📝 Notas Crédito y Débito** | `id` | `code` | Documento | Algoritmo | Requisitos especiales | |:----:|:------:|-----------|:---------:|----------------------| | **5** | 91 | Nota crédito | CUDE-SHA384 | Requiere [`discrepancy_response`](https://docs.matias-api.com/docs/billing-fields#discrepancy_response-) y [`billing_reference`](https://docs.matias-api.com/docs/billing-fields#billing_reference-) | | **4** | 92 | Nota débito | CUDE-SHA384 | Requiere [`discrepancy_response`](https://docs.matias-api.com/docs/billing-fields#discrepancy_response-) y [`billing_reference`](https://docs.matias-api.com/docs/billing-fields#billing_reference-) | **👷 Nómina Electrónica** | `id` | `code` | Documento | Algoritmo | Notas | |:----:|:------:|-----------|:---------:|:-----:| | **13** | 102 | Documento Soporte Nómina Electrónica | CUNE-SHA384 | ✅ | | **14** | 103 | Nota de Ajuste Nómina Electrónica | CUNE-SHA384 | ✅ | **🔔 Eventos y Otros** | `id` | `code` | Documento | Algoritmo | Descripción | |:----:|:------:|-----------|:---------:|-------------| | **12** | 96 | Eventos (ApplicationResponse) | CUDE-SHA384 | Acuse de recibo, aceptación/rechazo | | **99** | 999 | Attached Document | CUDS-SHA384 | Documento adjunto | **🎫 Otros Documentos Equivalentes (próximamente)** | `id` | `code` | Documento | Estado | |:----:|:------:|-----------|:------:| | **25** | 25 | Boleta de ingreso a cine | 🔜 Inactivo | | **27** | 27 | Boleta espectáculos públicos | 🔜 Inactivo | | **30** | 30 | Juegos localizados y no localizados | 🔜 Inactivo | | **35** | 35 | Tiquete transporte terrestre de pasajeros | 🔜 Inactivo | | **40** | 40 | Cobro de peajes | 🔜 Inactivo | | **45** | 45 | Extracto sociedades financieras y fondos | 🔜 Inactivo | | **50** | 50 | Tiquete transporte aéreo de pasajeros | 🔜 Inactivo | | **55** | 55 | Operación bolsa de valores y comodities | 🔜 Inactivo | | **60** | 60 | Servicios públicos domiciliarios | 🔜 Inactivo | - #### Ejemplo ```json "type_document_id": 7 ``` **Consultar también:** [Referencia Rápida de Tipos de Documento](https://docs.matias-api.com/docs/billing-fields#-referencia-rápida-de-tipos-de-documento) | [Glosario: Contingencia](https://docs.matias-api.com/docs/glossary#contingencia) ### `graphic_representation` 🟢 Indicador para solicitar que la API genere el **PDF de representación gráfica** del documento. _Este campo es opcional_. Enviar `1` para generar el PDF, `0` o no enviar si no se requiere. Debe ser un entero. - #### Ejemplo ```json "graphic_representation": 1 ``` ### `send_email` 🟢 Indicador para solicitar que la API **envíe automáticamente por email** el documento al cliente (al correo definido en `customer.email`). _Este campo es opcional_. Enviar `1` para enviar, `0` o no enviar si no se requiere. Debe ser un entero. - #### Ejemplo ```json "send_email": 1 ``` ### `currency_id` 🟢 ID de la moneda del documento (`cbc:DocumentCurrencyCode`). _Este campo es opcional_: si no se envía, la API asume **Peso Colombiano (COP)** (`id: 272`). Solo se debe enviar cuando el documento está en moneda extranjera. Debe ser un entero. :::warning Importante La moneda debe estar **habilitada en su cuenta** del proveedor tecnológico. Si envía un `currency_id` de una moneda no configurada en su cuenta, el API rechazará el documento. Consulte las monedas disponibles en el **ENDPOINT** `{{url}}/currencies`. ::: **💱 Monedas activas** | `id` | ISO | Moneda | Símbolo | |:----:|:---:|--------|:-------:| | **272** | COP | Peso Colombiano _(predeterminado)_ | $ | | **188** | USD | Dólar Americano | $ | | **213** | EUR | Euro | € | | **277** | MXN | Peso Mexicano | $ | | **270** | ARS | Peso Argentino | $ | | **271** | CLP | Peso Chileno | $ | | **194** | CAD | Dólar Canadiense | $ | | **266** | PEN | Nuevo Sol (Perú) | S | | **278** | UYU | Peso Uruguayo | $ | | **274** | DOP | Peso Dominicano | R | | **273** | CUP | Peso Cubano | $ | | **225** | PYG | Guaraní (Paraguay) | ₲ | | **165** | BOB | Boliviano (Bolivia) | B | | **164** | VEF | Bolívar Fuerte (Venezuela) | B | | **168** | CRC | Colón Costarricense | ₡ | | **169** | SVC | Colón Salvadoreño | ₡ | | **195** | GYD | Dólar de Guyana | G | - #### Ejemplo ```json "currency_id": 188 ``` ### `send_to_queue` 🟢 :::caution NO IMPLEMENTADO Este campo **aún no está disponible**. No lo envíe: con `send_to_queue: 1` la emisión **se rechaza** con HTTP `400` y no se consume el consecutivo. ```json { "success": false, "message": "El envío a cola de procesamiento aún no está habilitado." } ``` Para procesamiento asíncrono use [`POST /bulk/documents`](https://docs.matias-api.com/docs/endpoints/bulk-documents.md). ::: Indicador reservado para enviar el documento a una cola de procesamiento asíncrono. Omítalo o envíe `0`. En la respuesta de emisión, `send_to_queue` siempre es `0`. ### `rounding` 🟢 Valor de redondeo aplicado al total del documento (`cbc:PayableRoundingAmount`). _Este campo es opcional_ y debe ser un string con valor flotante. Se utiliza principalmente en **Nómina Electrónica** para ajustar centavos o decimales en el total pagable. :::info Uso principal Este campo aplica para documentos de **Nómina Electrónica** (`type_document_id: 13, 14`). En facturación electrónica estándar generalmente no se requiere. ::: ### `attachments`: **NEW** Archivos adjuntos opcionales cargados y vinculados al documento (máximo 4 archivos, almacenados de forma segura en Amazon S3). _Este campo es opcional (nullable)_ y debe ser un arreglo de objetos con los siguientes campos: **💻 JSON de ejemplo** ```json "attachments": [ { "filename": "Soporte.pdf", "content": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago...", "content_type": "application/pdf" } ] ``` **📎 Campos del adjunto** | Campo | Tipo | Requerido | Descripción / Reglas | |-------|------|-----------|----------------------| | `filename` | `string` | ⚠️ Condicional | Nombre del archivo adjunto con extensión. **Obligatorio si se envía `attachments`**. Max: 255 chars. | | `content` | `string` | ⚠️ Condicional | Contenido del archivo en base64. **Obligatorio si se envía `attachments`**. | | `content_type` | `string` | Opcional | Tipo MIME del archivo (ej. `application/pdf`, `image/png`). Max: 100 chars. | ### `invoice_period` 🟢 Periodo de facturación del documento a nivel general (`cac:InvoicePeriod`). Diferente al `invoice_period` dentro de `lines`. _Este campo es opcional_ y debe ser un objeto. :::tip ¿Cuándo se usa? - **Documento Soporte** (`type_document_id: 11`): para indicar el periodo de prestación del servicio. - **Sector Salud**: periodo de facturación del servicio médico. - **Notas Crédito/Débito sin referencia a factura** (`operation_type_id: 15, 16`): obligatorio para indicar el periodo que cubre la nota. ::: - #### Ejemplo ```json "invoice_period": { "start_date": "2024-01-01", "start_time": "00:00:00", "end_date": "2024-01-31", "end_time": "23:59:59" } ``` - #### Detalle de los campos - #### `start_date` Fecha de inicio del periodo. Formato `YYYY-MM-DD`. - #### `start_time` Hora de inicio del periodo. Formato `HH:mm:ss`. - #### `end_date` Fecha de fin del periodo. Formato `YYYY-MM-DD`. - #### `end_time` Hora de fin del periodo. Formato `HH:mm:ss`. ### `deliveries`: **NEW** Grupo de información de la entrega de bienes o prestación de servicios (`cac:Delivery`). _Este campo es opcional_ y debe ser un arreglo de objetos. Permite informar la dirección, fecha, transportador y contacto de la entrega física de mercancías. **📦 Ejemplo JSON completo** ```json "deliveries": [ { "date": "2026-05-21", "time": "14:00:00", "address": "NORTE DE VIRGINIA H10", "country_id": "239", "delivery_party": { "identity_document_id": "10", "type_organization_id": 1, "tax_regime_id": 2, "tax_level_id": "5", "company_name": "Nombre de la empresa transportadora", "address": "cra prueba", "dni": "1019016005", "country_id": "45", "city_id": "149", "postal_code": "110121", "merchant_registration": "12454" }, "delivery_contact": { "email": "cliente1@ejemplo.com", "mobile": "3164444444", "contact_name": "Nombre del contacto", "note": "Nota adicional de contacto" } } ] ``` **📍 Campos del delivery — Lugar y fecha de entrega** | Campo | Tipo | Requerido | XPath UBL | Descripción | |-------|:----:|:---------:|-----------|-------------| | `date` | string | Opcional | `cbc:ActualDeliveryDate` | Fecha real o estimada de entrega. Formato `YYYY-MM-DD` | | `time` | string | Opcional | `cbc:ActualDeliveryTime` | Hora real o estimada de entrega. Formato `HH:mm:ss` | | `address` | string | **Sí** | `cac:DeliveryAddress` | Dirección del lugar de entrega | | `country_id` | string | Opcional | `cac:Country` | ID del país de entrega. Por defecto `"45"` (Colombia). **Endpoint:** `{{url}}/countries` | | `city_id` | string | ⚠️ Condicional | `cac:DeliveryAddress` | Ciudad/municipio de entrega. **Obligatorio si `country_id = "45"` (Colombia)**. **Endpoint:** `{{url}}/cities` | | `postal_code` | string | ⚠️ Condicional | `cac:DeliveryAddress` | Código postal de entrega. **Obligatorio si `country_id = "45"` (Colombia)** | :::warning Entregas en Colombia Cuando el país de entrega es **Colombia** (`country_id: "45"`), los campos `city_id` y `postal_code` son **obligatorios**. La DIAN rechazará el documento si no se informan. ::: **🏢 delivery_party — Transportador o parte que entrega** Información fiscal y de identificación del transportador (`cac:DeliveryParty`). _Opcional_. | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `identity_document_id` | string | **Sí** | Tipo de documento de identidad. **Endpoint:** `{{url}}/identity-documents` | | `type_organization_id` | int | **Sí** | `1` = Persona Jurídica, `2` = Persona Natural | | `company_name` | string | **Sí** | Razón social o nombre completo del transportador | | `dni` | string | **Sí** | Número de identificación sin dígito de verificación | | `tax_regime_id` | int | Opcional | Régimen fiscal. Por defecto `2` (No responsable de IVA). **Endpoint:** `{{url}}/accounting-regime` | | `tax_level_id` | string | Opcional | Responsabilidad tributaria. Por defecto `"5"` (No aplica). **Endpoint:** `{{url}}/fiscal-regime` | | `address` | string | Opcional | Dirección fiscal del transportador | | `country_id` | string | Opcional | País del transportador. Por defecto `"45"` (Colombia) | | `city_id` | string | ⚠️ Condicional | Ciudad/municipio. **Obligatorio si `country_id = "45"`**. **Endpoint:** `{{url}}/cities` | | `postal_code` | string | ⚠️ Condicional | Código postal. **Obligatorio si `country_id = "45"`**. Por defecto `"000000"` | | `merchant_registration` | string | Opcional | Matrícula mercantil (`cbc:CorporateRegistrationScheme`) | **👤 delivery_contact — Contacto de la entrega** Persona responsable de recibir o coordinar la entrega (`cac:Contact`). _Opcional_. | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `contact_name` | string | Opcional | Nombre de la persona de contacto | | `email` | string | Opcional | Correo electrónico de contacto | | `mobile` | string | Opcional | Teléfono o celular de contacto | | `note` | string | Opcional | Nota adicional sobre la entrega | ### `delivery_terms` 🟢 Términos de entrega o condiciones INCOTERM del documento. _Este campo es opcional_ y debe ser un objeto. Aplicable en facturas de exportación o cuando se negocian condiciones de entrega. - #### Ejemplo ```json "delivery_terms": { "delivery_id": "1", "terms": "FOB" } ``` - #### Detalle de los campos - #### `delivery_id` ID de la condición de entrega. Puede consultar las diferentes condiciones en el **ENDPOINT** `{{url}}/delivery-conditions`. - #### `terms` Texto descriptivo de los términos de entrega. _Este campo es opcional_ y debe ser un string. ### `despatch_document_references` 🟢 Referencias a documentos de despacho asociados al documento electrónico. _Este campo es opcional_ y debe ser un arreglo de objetos. - #### Ejemplo ```json "despatch_document_references": [ { "number": "DESP-001", "date": "2024-02-28" } ] ``` - #### Detalle de los campos - #### `number` Número del documento de despacho referenciado. _Obligatorio_ y debe ser un string. - #### `date` Fecha del documento de despacho. Formato `YYYY-MM-DD`. ### `receipt_document_references` 🟢 Referencias a documentos de recepción asociados al documento electrónico. _Este campo es opcional_ y debe ser un arreglo de objetos. - #### Ejemplo ```json "receipt_document_references": [ { "number": "REC-001", "date": "2024-03-01" } ] ``` - #### Detalle de los campos - #### `number` Número del documento de recepción referenciado. _Obligatorio_ y debe ser un string. - #### `date` Fecha del documento de recepción. Formato `YYYY-MM-DD`. ### `showroomInformation` 🟢 :::caution NO IMPLEMENTADO Este campo **aún no está disponible** en la versión actual de la API. Se documenta como referencia para futuras versiones. No lo envíe en el request body, será ignorado. ::: Información de la sala de ventas o punto de exhibición. _Este campo es opcional_ y debe ser un objeto. Utilizado cuando la venta se realiza en una ubicación diferente a la sede principal. - #### Ejemplo ```json "showroomInformation": { "showroom": "Sala de Ventas Norte", "showroomAddress": "Cra 15 #100-45 Local 201", "dataShow": "Información adicional de la sala" } ``` ### `prepaid_payments` 🟢 Información de anticipos recibidos que se deben descontar del total a pagar del documento. _Este campo es opcional_ y debe ser un objeto. - #### Ejemplo ```json "prepaid_payments": { "id": "SFR3123856", "paid_amount": "10.00", "received_date": "2018-09-29", "paid_date": "2018-09-29", "instruction_id": "Prepago recibido" } ``` - #### Detalle de los campos - #### `id` Identificador del anticipo o prepago. _Este campo es obligatorio_ y debe ser un string. - #### `paid_amount` Monto del anticipo pagado. _Este campo es obligatorio_ y debe ser un string con valor flotante de máximo dos decimales. - #### `received_date` Fecha en que se recibió el anticipo. _Este campo es obligatorio_ y debe ser un string en formato `YYYY-MM-DD`. - #### `paid_date` Fecha en que se realizó el pago del anticipo. _Este campo es obligatorio_ y debe ser un string en formato `YYYY-MM-DD`. - #### `instruction_id` Descripción o instrucción del prepago. _Este campo es opcional_ y debe ser un string. ### `payments` 🔴 Información de los pagos del documento. Define el método (contado/crédito), medio de pago y valor. Permite múltiples pagos. _Este campo es obligatorio_ para todos los documentos y debe ser un arreglo de objetos. - #### Ejemplo ```json "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "141100.00", "payment_due_date": "2024-02-22" } ] ``` - ### Detalle de los campos - #### `payment_method_id` Método de pago: **`1`** = Contado, **`2`** = Crédito. _Este campo es obligatorio_ y debe ser un entero. - #### `means_payment_id` Medio de pago (ej. `10` = Efectivo, `42` = Consignación bancaria, `47` = Transferencia). _Este campo es obligatorio_ y debe ser un entero. Puede consultar todos los medios de pago disponibles en el **ENDPOINT** `{{url}}/payment-means`. - #### `value_paid` Valor pagado. _Este campo es obligatorio_ para todos los documentos y debe ser un número flotante con máximo dos decimales, encerrado entre `""`. - #### `payment_due_date` Fecha de vencimiento del pago. Este campo es usado para indicar la fecha de vencimiento de un pago a **crédito**. **Es obligatorio solo para las ventas a crédito** y debe ser un string en formato `YYYY-MM-DD`. - #### `duration_measure` Duración de la medida del pago en días. _Este campo es opcional_ y se utiliza para indicar la duración del periodo de pago a crédito. Debe ser un string. ### `report_header`: **NEW** Plantilla para personalizar el encabezado y pie de página de la representación gráfica (PDF). La API renderiza dinámicamente los valores proporcionados sobre una plantilla prediseñada. _Este campo es opcional_ y debe ser un objeto. :::info ¿Cómo funciona? Se proporciona el `uuid` de una plantilla previamente creada en el sistema y un arreglo de `vars` con los valores a inyectar. La API busca la plantilla y reemplaza los marcadores de posición (placeholders) con los valores proporcionados. ::: **🧩 Estructura del objeto** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `uuid` | string | **Sí** | UUID de la plantilla de diseño previamente creada en el sistema | | `vars` | array | **Sí** | Lista de variables para reemplazar los placeholders de la plantilla | **Cada objeto dentro de `vars`:** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `name` | string | **Sí** | Nombre del placeholder **sin llaves `{}`**. Ej: si el marcador es `{sucursal}`, enviar `"sucursal"` | | `value` | string | **Sí** | Valor de texto o HTML que reemplazará el marcador. Se inserta tal cual en la plantilla | **📦 Ejemplo JSON** ```json "report_header": { "uuid": "101413670038274164", "vars": [ { "name": "sucursal", "value": "Bodega Principal Cali" }, { "name": "direccion", "value": "Zona Franca Palmaseca Bodega 5" }, { "name": "celular", "value": "315 112 4411" } ] } ``` ### `due_diligence`: **NEW** - Información para control cambiario - **Control Cambiario - Debida Diligencia**. Los tipos de operación que se encuentran permitidos son los que se encuentran en la columna “Código” que están dentro de la tabla 18.3 Control Cambiario - Debida Diligencia, ubicada en la Caja de Herramientas en la siguiente ruta “Caja_de_herramientas_Factura_Electronica_Validacion_Previa.zip\Anexo Tecnico\Tablas Referenciadas”, en formato Excel “.xlsx” - #### Ejemplo ```json "due_diligence": { "code": "01" } ``` - #### Tabla debida diligencia - Control cambiario | Código | Descripción | | ------ | ----------------------------------------------------------------------------------------------- | | 01 | Debida Diligencia del Cliente – DDC General | | 02 | Debida Diligencia del Cliente – DDC Reforzada | | 03 | Debida diligencia intensificada por razón de la cuantía de las operaciones – DDC intensificada. | | 04 | Debida Diligencia del Cliente – DDC simplificada | ### `document_signature`: **NEW** Información de la firma del documento. _Este campo es opcional_ y debe ser un objeto. - #### Ejemplo ```json "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" } ``` - #### Detalle de los campos - #### `cashier` Nombre del cajero(a). _Este campo es opcional_ y debe ser un string. - #### `cashier_title` Título o etiqueta que se muestra debajo de la firma del cajero en la representación gráfica. _Este campo es opcional_ y debe ser un string. - #### `seller` Nombre del vendedor(a). _Este campo es opcional_ y debe ser un string. - #### `seller_title` Título o etiqueta que se muestra debajo de la firma del vendedor en la representación gráfica. _Este campo es opcional_ y debe ser un string. ### `payment_exchange_rate` 🟡 Tasa de cambio aplicada al documento. _Este campo es obligatorio_ cuando `currency_id` indica una moneda extranjera (diferente a COP). Define la equivalencia entre la moneda extranjera y el Peso Colombiano. Debe ser un objeto. - #### Ejemplo ```json "payment_exchange_rate": { "exchange_rate": "3950.00", "rate_date": "2022-06-28", "currency_id": 188 } ``` - #### Detalle de los campos - #### `exchange_rate` Valor equivalente de **1 unidad de la moneda extranjera en Pesos Colombianos (COP)**. Por ejemplo, si el documento es en USD y 1 USD = 3950 COP, el valor debe ser `"3950.00"`. _Este campo es obligatorio_ y debe ser un string con valor flotante. - #### `rate_date` Fecha de la tasa de cambio utilizada. _Este campo es obligatorio_ y debe ser un string en formato `YYYY-MM-DD`. - #### `currency_id` ID de la moneda extranjera del documento. _Este campo es obligatorio_ y debe ser un entero. Consulte los valores en [`currency_id`](https://docs.matias-api.com/docs/billing-fields#currency_id-). ### `point_of_sale` 🟡 Información del punto de venta físico donde se realizó la transacción. _Este campo es obligatorio_ para documentos de tipo **P.O.S Electrónico** (`type_document_id = 20`) y debe ser un objeto. **📦 Ejemplo JSON** ```json "point_of_sale": { "cashier_name": "LEWIS LOPEZ", "terminal_number": "CJ001aB", "cashier_type": "Caja de apoyo", "sales_code": "POS01", "address": "Gilbarco Encore 4 L1 Mangue ra 17 AC", "sub_total": "2000.10" } ``` **🏪 Campos del punto de venta** | Campo | Tipo | Descripción | |-------|:----:|-------------| | `cashier_name` | string | Nombre del cajero que realiza la venta | | `terminal_number` | string | Número de terminal del punto de venta (ej. `"CJ001aB"`) | | `cashier_type` | string | Tipo de caja (ej. `"GENÉRICA"`, `"Caja de apoyo"`) | | `sales_code` | string | Código o ID de la venta (ej. `"POS01"`, `"45212"`) | | `address` | string | Dirección física del punto de venta | | `sub_total` | string | Subtotal de la venta (total sin IVA). Formato `"0.00"` | :::info Todos los campos son **obligatorios** para `type_document_id = 20`. ::: ### `software_manufacturer` 🟡 Información del fabricante del software POS que generó el documento. _Este campo es obligatorio_ para documentos equivalentes P.O.S (`type_document_id = 20`) y debe ser un objeto. - #### Ejemplo ```json { "owner_name": "LEWIS LOPEZ GOMEZ", "company_name": "LOPEZSOFT SAS", "software_name": "SOFTWARE POS MATIAS APP" } ``` - #### Detalle de los campos - #### `owner_name` Nombre del propietario. _Este campo es obligatorio_ solo para los documentos equivalentes P.O.S y debe ser un string. - #### `company_name` Nombre de la compañía. _Este campo es obligatorio_ solo para los documentos equivalentes P.O.S y debe ser un string. - #### `software_name` Nombre del software. _Este campo es obligatorio_ solo para los documentos equivalentes P.O.S y debe ser un string. ### `order_reference` 🟢 Referencia de la orden de compra. _Este campo es opcional_ y debe ser usado de acuerdo al giro del documento. Debe ser un objeto. **Diferencia con `additional_document_reference`:** - Use `order_reference` para **UNA SOLA** orden de compra - Use [`additional_document_reference`](https://docs.matias-api.com/docs/billing-fields#additional_document_reference-referencia-a-documento-adicional) para **MÚLTIPLES** documentos de referencia - #### Ejemplo ```json "order_reference": { "reference_number": "4545478787", "reference_date": "2021-05-19" } ``` - #### Detalle de los campos - #### `reference_number` Número de referencia. _Este campo es opcional_ debe ser usado de acuerdo al giro del documento y debe ser un string. - #### `reference_date` Fecha de referencia. _Este campo es opcional_ debe ser usado de acuerdo al giro del documento y debe ser un string. ### `health` 🟢 Información específica y obligatoria para documentos electrónicos del **Sector Salud** (reglamentada por la **Resolución 000948 de 2026** [14 de mayo de 2026] del Ministerio de Salud y Protección Social —que deroga expresamente las Resoluciones 2275 de 2023, 558 y 1884 de 2024—, Resolución 866 de 2021 y el **Documento Técnico 2 versión 001 - FEV Sector Salud** del MinSalud / DIAN). _Este campo es requerido_ cuando el emisor sea un prestador de servicios de salud (IPS, profesional independiente, transporte asistencial) o proveedor de tecnologías en salud que facture a EPS, entidades territoriales, ADRES, aseguradoras SOAT o planes voluntarios de salud. Debe ser un objeto. **📋 Ejemplo de Payload JSON — Objeto health** ```json "health": { "operation_type": "SS-CUFE", "provider_code": "2341702036", "payment_modality": "02", "coverage": "01", "contract_number": "c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c", "policy_number": "", "copayment": 0, "moderator_fee": 0, "shared_payments": 0, "advance": 0, "justification_without_contract": "", "invoice_period": { "start_date": "2026-07-01", "start_time": "00:00:00", "end_date": "2026-07-30", "end_time": "23:59:59" }, "download_attachments": { "url": "www.ips-1.com.co", "arguments": [ { "name": "excelFile", "value": "a1b2c3.xlsx" }, { "name": "txtFile", "value": "a1b2c3.txt" } ] }, "document_delivery": { "ws": "https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl", "arguments": [ { "name": "Método-1", "value": "ClienteEntregaAcuseDeReciboDeFEV-VP" }, { "name": "Método-2", "value": "ClienteEntregaConstanciaDeMercanciaEntregada" } ] } } ``` **🏥 Campos Principales del Prestador y Contrato (Extensión Salud)** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `operation_type` | string | ✅ Sí | Tipo de operación del sector salud. Valores habituales: `"SS-CUFE"` (Factura electrónica con CUFE), `"SS-CUDE"` (Notas débito/crédito), `"SS-POS"`, `"SS-SinAporte"`. | | `provider_code` | string | ✅ Sí | Código asignado en el SGSSS al prestador en el Registro Especial de Prestadores de Servicios de Salud (**REPS** - tabla `IPSCodHabilitación`) o código asignado por MinSalud (tabla `IPSnoREPS`) para Proveedores de Tecnologías en Salud. | | `payment_modality` | string | ✅ Sí | Código de la modalidad de pago pactada objeto de facturación (Catálogo `modalidadPago` de SISPRO). En facturas multiusuario, todos deben compartir la misma modalidad. | | `coverage` | string | ✅ Sí | Código de la entidad/fuente responsable de financiar la cobertura o plan de beneficios (Catálogo `coberturaPlan` de SISPRO). | | `contract_number` | string | ⚠️ Condicional | Número del contrato suscrito objeto de facturación. Si la entidad está obligada por la Ley 1966/2019, debe registrar el código **CUCON** expedido por la plataforma de contratación. En caso de no existir contrato, enviar vacío `""`. | | `policy_number` | string | ⚠️ Condicional | Número de la póliza de seguro. **Obligatorio** cuando se trate de atenciones cubiertas por póliza **SOAT** (cobertura `04`) o planes voluntarios de salud (pólizas de salud `12`). En los demás casos debe ir vacío `""`. | | `justification_without_contract` | string | ⚠️ Condicional | Código de justificación de atención cuando se factura sin contrato previo con el pagador (Catálogo `facturaSinContrato` de SISPRO). Obligatorio según causal específica (ej. urgencias, tutelas, portabilidad). Si hay contrato, enviar vacío `""`. | :::info Campos que el API exige Cuando envías `health`, el API exige `operation_type`, `provider_code`, `payment_modality`, `coverage` e `invoice_period` (con sus fechas y horas). `contract_number` y `policy_number` son opcionales para el API: si se omiten se informan vacíos (`""`). Si omites `contract_number` (o lo envías `null`), el API exige `justification_without_contract` y responde `422` sin él: `El campo health.justification_without_contract es requerido cuando no se envía health.contract_number`. Si hay contrato, envía `contract_number` y deja `justification_without_contract` vacío. Las columnas ⚠️ Condicional indican cuándo la normativa del sector salud exige el valor real. ::: --- #### 📌 Catálogo de Modalidades de Pago (`payment_modality`) | Código | Modalidad | Definición (Decreto 780 de 2016) | |:---:|---|---| | `01` | **Pago individual por caso / Paquete / Canasta** | Conjunto integral de atenciones por patología, grupo relacionado de diagnóstico o procedimiento quirúrgico. | | `02` | **Pago global prospectivo** | Tarifa global pactada anticipadamente para cubrir un volumen o grupo de servicios durante un periodo acordado. | | `03` | **Pago por capitación** | Pago anticipado de una suma fija por persona que se pacta para atender un conjunto definido de servicios durante un periodo. | | `04` | **Pago por evento** | Mecanismo en el que el pago se realiza por cada actividad, procedimiento, intervención, insumo o medicamento efectivamente prestado. | --- #### 📌 Catálogo de Cobertura o Plan de Beneficios (`coverage`) | Código | Cobertura / Plan de Beneficios | Descripción y Base Normativa | |:---:|---|---| | `01` | **UPC Contributiva** | Plan de beneficios en salud financiado con UPC contributiva (Resolución 2765 de 2025). | | `02` | **Presupuesto Máximo** | Servicios y tecnologías no financiados con UPC y no excluidos de la financiación con recursos del SGSSS (Resolución 067 de 2025). | | `03` | **Prima EPS, no asegurados SOAT** | Víctimas de accidentes de tránsito ocasionados por vehículos no asegurados por SOAT o no identificados (Art. 106 D.L. 2106/2019). | | `04` | **Cobertura póliza SOAT** | Servicios prestados a víctimas de accidentes con póliza SOAT vigente (Decreto 780 de 2016). Requiere `policy_number`. | | `05` | **Cobertura ARL** | Prestaciones asistenciales derivadas de accidentes de trabajo y enfermedad laboral a cargo de la ARL (Dec. 1295/1994). | | `06` | **Cobertura ADRES** | Coberturas en salud a cargo de la Administradora de los Recursos del SGSSS (Ley 1955/2019). | | `07` | **Cobertura Salud Pública** | Acciones colectivas de gestión en salud pública Nación / Entidades Territoriales (Res. 1597 de 2025). | | `08` | **Entidad Territorial (Recursos de Oferta)** | Prestación a cargo de la entidad territorial para población no afiliada al SGSSS (Leyes 715/2001 y 1955/2019). | | `09` | **Urgencias Población Migrante** | Atención de urgencias a población migrante regular no afiliada o irregular (Art. 232 Ley 1955/2019). | | `10` | **Plan Complementario en Salud** | Coberturas contratadas voluntariamente financiadas por afiliado/empleador (Dec. 780/2016). | | `11` | **Plan Medicina Prepagada** | Coberturas de medicina prepagada contratadas voluntariamente (Dec. 780/2016). | | `12` | **Pólizas en Salud** | Pólizas de hospitalización y cirugía contratadas voluntariamente (Dec. 780/2016). Requiere `policy_number`. | | `13` | **Régimen Especial o Excepción** | Servicios a regímenes de excepción: Fuerzas Militares, Policía, Magisterio, Ecopetrol, Universidades (Ley 647/2001). | | `14` | **Fondo Nacional PPL / INPEC** | Población privada de la libertad en custodia del INPEC (Ley 1709 de 2014 / Dec. 1069 de 2015). | | `15` | **Particular** | Servicios y tecnologías asumidos directamente por el paciente con recursos propios. | | `16` | **UPC Subsidiada** | Plan de beneficios en salud financiado con UPC subsidiada (Resolución 2765 de 2025). | --- #### 📌 Causales de Factura Sin Contrato (`justification_without_contract`) Se diligencia exclusivamente cuando el prestador no cuenta con acuerdo de voluntades previo con el pagador: 1. Atención de urgencias vitales. 2. Atención a cargo de ADRES, aseguradora SOAT o pólizas voluntarias / medicina prepagada. 3. Atención en salud por fallos de tutela u órdenes judiciales. 4. Atención en salud por Portabilidad o asignación masiva de afiliados (Art. 2.5.3.4.7.9 Decreto 780/2016). 5. Atención excepcional por cotizaciones o autorizaciones adicionales de servicios. 6. Gestión y recuperación de órganos para trasplante (Ley Estatutaria 1751 de 2015). 7. Profesionales independientes en atención a pacientes particulares. **💰 Copagos, Cuotas Moderadoras, Pagos Compartidos y Anticipos (PrepaidPayment)** Todos los montos numéricos deben ser mayores o iguales a `0`, expresados sin separadores de miles y con punto decimal. Estos valores se descuentan o informan de acuerdo con las reglas de interoperabilidad con **RIPS**. Para el API los cuatro montos son **opcionales** (aceptan `null`): si no se envían, se informan como `0`. Aun así, la DIAN y la validación de RIPS pueden exigir el valor real recaudado, así que envíalos siempre que apliquen. | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `copayment` | number | No (por defecto `0`) | Valor total efectivamente recaudado por el prestador correspondiente a **Copago**. En facturas multiusuario corresponde a la sumatoria de copagos. Debe coincidir exactamente con el valor reportado en el JSON de RIPS. | | `moderator_fee` | number | No (por defecto `0`) | Valor efectivamente recaudado por concepto de **Cuota Moderadora**. En facturas multiusuario es la sumatoria de cuotas. Debe coincidir con el valor de RIPS. | | `shared_payments` | number | No (por defecto `0`) | Valor recaudado por **Pagos Compartidos** en planes voluntarios de salud (medicina prepagada, pólizas, planes complementarios). Debe coincidir con RIPS. | | `advance` | number | No (por defecto `0`) | Valor del **Anticipo** a legalizar que se descuenta del valor de la factura en ejecución del contrato pactado. | **📅 Periodo de Facturación (invoice_period)** Define el lapso temporal de la prestación de los servicios de salud (obligatorio para usuario único o facturación multiusuario / capitación): | Campo | Tipo | Requerido | Formato | Descripción | |-------|:----:|:---------:|:-------:|-------------| | `start_date` | string | ✅ Sí | `YYYY-MM-DD` | Fecha de inicio de prestación del servicio o inicio del periodo facturado (ej. `"2026-07-01"`). | | `start_time` | string | ✅ Sí | `HH:mm:ss` | Hora de inicio de la atención o inicio de periodo (ej. `"00:00:00"`). | | `end_date` | string | ✅ Sí | `YYYY-MM-DD` | Fecha final de prestación del servicio o fin del periodo facturado (ej. `"2026-07-30"`). | | `end_time` | string | ✅ Sí | `HH:mm:ss` | Hora final de la atención o cierre de periodo (ej. `"23:59:59"`). | **📎 Descarga de Adjuntos y Entrega Electrónica (download_attachments / document_delivery)** Permite configurar canales automatizados para la descarga de soportes clínicos y la entrega de acuses entre el prestador y el pagador/ERP. Ambos bloques son **opcionales**: si no se envían, no se incluyen en el XML. Si se envían, `url` / `ws` toman `""` y `arguments` toma `[]` cuando se omiten. #### Objeto `download_attachments` (Descarga de Soportes) | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `url` | string | No | Dominio o URL del servicio web donde el pagador puede descargar los anexos y soportes clínicos (ej. `"www.ips-1.com.co"`). | | `arguments` | array | No | Arreglo de objetos `{ "name": "...", "value": "..." }` con los nombres y rutas de los archivos adjuntos (ej. soportes en Excel, PDF de historia clínica o archivos RIPS). | #### Objeto `document_delivery` (Integración de Web Service) | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `ws` | string | No | Endpoint WSDL/SOAP o REST para la recepción de acuses de recibo y constancias de entrega de la factura electrónica. Si se envía, debe ser una URL válida. | | `arguments` | array | No | Arreglo de objetos `{ "name": "...", "value": "..." }` con métodos o parámetros de consumo (ej. `"ClienteEntregaAcuseDeReciboDeFEV-VP"`). | --- ### `customer` 🔴 Información del cliente o proveedor. _Este campo es obligatorio_ para todos los documentos y debe ser un objeto. **📄 Factura Electrónica y Documento Soporte** | Campo | Tipo | Requerido | Default | Descripción | |-------|:----:|:---------:|:-------:|-------------| | `company_name` | string | **Sí** | — | Razón social o nombre de persona natural | | `dni` | string | **Sí** | — | Número de identificación **sin dígito de verificación** | | `email` | string | **Sí** | — | Correo para envío del documento electrónico | | `identity_document_id` | int | Opcional | `3` (NIT) | Tipo de documento de identidad. **Endpoint:** `{{url}}/identity-documents` | | `type_organization_id` | int | Opcional | `2` | `1` = Persona Jurídica, `2` = Persona Natural | | `tax_regime_id` | int | Opcional | `2` | Régimen fiscal (código `49` = No responsable IVA). **Endpoint:** `{{url}}/accounting-regime` | | `tax_level_id` | int | Opcional | `5` | Responsabilidad tributaria (`R-99-PN` = No aplica). **Endpoint:** `{{url}}/fiscal-regime` | | `country_id` | int | Opcional | `45` | País del cliente (Colombia). **Endpoint:** `{{url}}/countries` | | `city_id` | int | Opcional | — | Ciudad del cliente. **Endpoint:** `{{url}}/cities` | | `address` | string | Opcional | `""` | Dirección del cliente | | `postal_code` | string | Opcional | `"000000"` | Código postal del cliente | | `mobile` | string | Opcional | `""` | Teléfono o celular del cliente | | `city_name` | string | Opcional | — | **NEW** — Nombre de la ciudad. Solo para clientes/proveedores extranjeros | | `extra_data` | array | Opcional | — | **NEW** — Información adicional para la representación gráfica (no se envía a DIAN) | **🏷️ extra_data — Campos adicionales del cliente** Arreglo de objetos `{title, value}` con datos propios del adquiriente que se muestran en el PDF pero **no se envían a la DIAN**. Útil para mostrar número de socio, categoría, fecha de vinculación, etc. ```json "extra_data": [ { "title": "No. Socio", "value": "78-54121-454" }, { "title": "FECHA DE VINCULACIÓN", "value": "02/02/2026" } ] ``` | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `title` | string | **Sí** | Etiqueta del campo. Máx. 60 caracteres | | `value` | string | **Sí** | Valor a mostrar. Máx. 500 caracteres | :::tip ¿Dato del cliente o del documento? Si el dato es sobre la **operación** (centro de costo, orden de servicio), usa [`additional_data`](https://docs.matias-api.com/docs/billing-fields#additional_data-) a nivel de documento. `customer.extra_data` es exclusivamente para datos que **describen al adquiriente**. ::: **🏪 Documento P.O.S Electrónico — Campos del cliente POS** | Campo | Tipo | Requerido | Default | Descripción | |-------|:----:|:---------:|:-------:|-------------| | `company_name` | string | **Sí** | — | Nombre de la empresa o persona natural | | `dni` | string | **Sí** | — | Número de identificación sin dígito de verificación | | `email` | string | Opcional | — | Correo para envío del documento | | `points` | int | Opcional | `0` | Puntos acumulados del cliente | ### `discrepancy_response` 🟢 Respuesta a discrepancias. _Este campo es obligatorio_ solo para las notas de crédito, débito y de ajustes de todos los documentos y debe ser un objeto. - #### Ejemplo ```json "discrepancy_response": { "reference_id": "EPOS2", "response_id": "9" } ``` - #### Detalle de los campos - #### `reference_id` Número del documento al que se le hace la nota, con el prefijo, **ejemplo(FE4578)**. _Este campo es obligatorio_ solo para las notas de crédito, débito y de ajustes de todos los documentos y debe ser un string. - #### `response_id` Hace referencia al tipo de corrección aplicado a la nota. _Este campo es obligatorio_ solo para las notas de crédito, débito y de ajustes de todos los documentos y debe ser un string. Puede consultar los diferentes tipos de corrección en el **ENDPOINT** `{{url}}/correction-notes`. ### `billing_reference` 🟡 - 🔴 **Obligatorio** para Notas Crédito/Débito (referencia a factura original) - 🟢 **Opcional** para otros documentos Referencia al documento original sobre el cual se emite la nota. _Debe ser un objeto_. **📦 Ejemplo JSON** ```json "billing_reference": { "number": "EPOS2", "date": "2023-12-22", "uuid": "b1b5d93a2918...031aea39", "scheme_name": "CUDE-SHA384" } ``` **🧾 Campos de la referencia** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `number` | string | **Sí** | Número del documento referenciado **con prefijo** (ej. `"FE4578"`) | | `date` | string | **Sí** | Fecha del documento referenciado. Formato `YYYY-MM-DD` | | `uuid` | string | **Sí** | CUFE o CUDE del documento referenciado | | `scheme_name` | string | ⚠️ Condicional | **Obligatorio** para notas POS Electrónico y notas de ajuste Doc. Soporte. Valores: `CUFE-SHA384`, `CUDE-SHA384`, `CUDS-SHA384` | ### `allowance_charges` 🟢 Descuentos o cargos **a nivel de factura** que no afectan las bases gravables. Los que afectan bases gravables se informan a nivel de ítem. _Este campo es opcional_ y debe ser un arreglo de objetos. **📦 Ejemplo JSON** ```json "allowance_charges": [ { "amount": "10000", "base_amount": "725000", "charge_indicator": true, "allowance_charge_reason": "Motivo del cargo a la factura" }, { "amount": "10000", "base_amount": "725000", "charge_indicator": false, "discount_id": 2, "allowance_charge_reason": "Motivo del descuento a la factura" } ] ``` **🧾 Campos del cargo/descuento** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `charge_indicator` | boolean | **Sí** | `true` = Cargo (aumenta el total), `false` = Descuento (reduce el total) | | `amount` | string | **Sí** | Valor del cargo o descuento. Si es descuento, no puede superar `base_amount` | | `base_amount` | string | **Sí** | Valor base sobre el cual se calcula el cargo o descuento | | `allowance_charge_reason` | string | **Sí** | Razón o motivo del cargo/descuento | | `discount_id` | int | ⚠️ Condicional | **Obligatorio si es descuento** (`charge_indicator: false`). **Endpoint:** `{{url}}/discount-codes` | | `multiplier_factor_numeric` | string | Opcional | Porcentaje como decimal (ej. `"10.00"` = 10%) | ### `legal_monetary_totals` 🔴 Totales monetarios del documento. Agrupa los valores brutos, impuestos, cargos, descuentos y el total a pagar. La DIAN valida que estos valores sean **matemáticamente consistentes** con las líneas y los impuestos. _Este campo es obligatorio_ y debe ser un objeto. **📦 Ejemplo JSON** ```json "legal_monetary_totals": { "line_extension_amount": "50000", "tax_exclusive_amount": "50000", "tax_inclusive_amount": "59500", "total_charges": 0, "total_allowance": 0, "payable_amount": "59500" } ``` **🔴 Campos obligatorios** | Campo | Tipo | Descripción | |-------|:----:|-------------| | `line_extension_amount` | string | Suma de las líneas antes de impuestos **(Valor Bruto)** | | `tax_exclusive_amount` | string | Base imponible total. Si no hay impuestos = `"0"` | | `tax_inclusive_amount` | string | Valor Bruto + tributos de todas las líneas | | `payable_amount` | string | **Total a pagar:** ítems + tributos + cargos globales − descuentos globales | **🟢 Campos opcionales** | Campo | Tipo | Default | Descripción | |-------|:----:|:-------:|-------------| | `total_charges` | string | `0` | Suma de todos los cargos globales | | `total_allowance` | string | `0` | Suma de todos los descuentos globales | | `allowance_total_amount` | string | — | Alternativa detallada a `total_allowance` | | `charge_total_amount` | string | — | Alternativa detallada a `total_charges` | | `pre_paid_amount` | string | — | Anticipos o prepagos que se descuentan del total | ### `lines` 🔴 Líneas de detalle del documento. Cada elemento del arreglo representa un ítem (producto o servicio) con su cantidad, precio, impuestos y descripción. Mínimo **1 línea** por documento. _Este campo es obligatorio_ y debe ser un arreglo de objetos. **📋 Campos obligatorios del ítem** | Campo | Tipo | Default | Descripción | |-------|:----:|:-------:|-------------| | `invoiced_quantity` | string | — | Cantidad del producto o servicio | | `line_extension_amount` | string | — | Valor total de la línea sin impuesto: **C × PU - D + R**. Formato `"0.00"` | | `description` | string | — | Descripción del artículo o servicio | | `code` | string | — | Código interno del artículo o servicio | | `price_amount` | string | — | Valor unitario del artículo o servicio. Formato `"0.00"` | | `base_quantity` | string | — | Cantidad sobre la que aplica el precio. Se recomienda igual a `invoiced_quantity` | | `quantity_units_id` | string | `1093` | Unidad de medida. **Endpoint:** `{{url}}/quantity-units` | | `type_item_identifications_id` | string | `4` | Estándar de identificación del ítem. **Endpoint:** `{{url}}/type-item-identifications` | | `reference_price_id` | string | `1` | Precio de referencia. **Endpoint:** `{{url}}/reference-price` | | `free_of_charge_indicator` | boolean | `false` | `true` = producto gratis o muestra | **📝 Campos opcionales del ítem** | Campo | Tipo | Descripción | |-------|:----:|-------------| | `note` | string | Nota adicional. **Obligatorio para AIU**, debe iniciar con: `"Contrato de servicios AIU por concepto de:"` | | `brand_name` | string | Marca del producto. Se muestra en la representación gráfica | | `model_name` | string | Modelo del producto | | `sellers_item_identification` | string | Código interno alterno del vendedor | | `pack_size_numeric` | string | Unidades por paquete o empaque | | `notes` | string / array | Notas adicionales a nivel de línea | | `um` / `mu` / `unit_measure_code` | string | Unidad de medida alternativa para la representación gráfica | ### `lines->mandate` Grupo de información que describe el mandatario de la operación de venta. _Este campo es obligatorio solo para facturas de mandato_ y debe ser un objeto. Este campo se informa a nivel de ítem y aplica solo para mandatos. Un mandante por ítem. - #### Ejemplo ```json "mandate": { "dni": "2222222222", "dv": "2", "code": "0" } ``` - #### Detalle de los campos - #### `dni` Número de identificación del mandatario. _Este campo es obligatorio_ y debe ser un string. - #### `dv` Dígito de verificación del mandatario. _Este campo es obligatorio_ y debe ser un string. - #### `code` Código que indica el tipo de ingreso del mandatario. _Este campo es obligatorio_ y debe ser un string. | Código | Significado | | ------ | ------------------------------------ | | 0 | B/S ingreso propio | | 1 | B/S Ingresos Recibidos para Terceros | ### `linea->extra_data`: **NEW** Genera **columnas dinámicas** en el detalle del PDF. Los datos se muestran en la representación gráfica del documento pero **no se envían a la DIAN**. _Este campo es opcional_ y debe ser un arreglo de objetos. :::info Columnas consistentes El campo `title` debe ser **idéntico** en todas las líneas que comparten la misma columna. La plantilla lo usa como encabezado de columna único; si varía entre líneas, se generan columnas distintas. ::: **📋 Campos disponibles** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `title` | string | **Sí** | Encabezado de la columna en el PDF | | `value` | string | **Sí** | Valor de la celda en esa línea | | `align` | string | No | `left`, `center`, `right`. Default: `left` | | `position` | int | No | 1–7. Indica después de qué columna base se inserta. Fuera de rango → comportamiento legacy | **🗺️ Guía de Posicionamiento (PDF)** El valor de `position` indica **después de qué columna base** se inserta la columna dinámica: | `position` | Columna insertada después de... | Ejemplo visual | |:----------:|--------------------------------|----------------| | `1` | **CÓDIGO** | `CÓDIGO \| 👉 MI_CAMPO \| DETALLE \| CANT \| ...` | | `2` | **DETALLE** | `CÓDIGO \| DETALLE \| 👉 MI_CAMPO \| CANT \| ...` | | `3` | **CANT** | `... \| CANT \| 👉 MI_CAMPO \| U.M \| ...` | | `4` | **U.M** | `... \| U.M \| 👉 MI_CAMPO \| PRECIO \| ...` | | `5` | **PRECIO** | `... \| PRECIO \| 👉 MI_CAMPO \| DESCUENTO \| ...` | | `6` | **DESCUENTO** (si aplica) | `... \| DESCUENTO \| 👉 MI_CAMPO \| RECARGO \| ...` | | `7` | **RECARGO** (si aplica) | `... \| RECARGO \| 👉 MI_CAMPO \| IVA \| ...` | | _sin valor_ | _(comportamiento legacy)_ | Después de recargos, antes de impuestos | :::warning Reglas - **Rango válido:** 1–7. Valores mayores a 7 se ignoran silenciosamente (comportamiento legacy). - **Resolución de conflictos:** Si múltiples líneas definen el mismo `title` con diferente `position`, la **primera línea** que define el título determina la posición para toda la tabla. - `position` es **por columna** (`title`), no por línea individual. Todas las líneas del documento comparten el mismo layout. ::: **📝 Ejemplo JSON completo** ```json "invoice_lines": [ { "code": "PROD-001", "description": "Producto de ejemplo", "extra_data": [ { "title": "LOTE", "value": "L-2026-001", "position": 2 }, { "title": "FECHA VENCIMIENTO", "value": "2026-12-31", "align": "center" } ] } ] ``` :::tip Resultado visual en el PDF ``` CÓDIGO | DETALLE | LOTE | CANT | U.M | PRECIO | FECHA VENCIMIENTO | IVA | TOTAL (position=2) (sin position → legacy) ``` ::: ### `lines->invoice_period` Periodo de facturación de la línea del documento soporte. _Este campo es obligatorio_ solo para los documentos soporte y debe ser un objeto. - #### Ejemplo ```json "invoice_period": { "start_date": "2022-06-28", "description_code": 1 } ``` - #### Descripción de los campos - #### `start_date` Fecha de inicio. _Este campo es obligatorio_ solo para los documentos soporte y debe ser un string. - #### `description_code` Código de descripción. _Este campo es obligatorio_ solo para los documentos soporte y debe ser un entero. ### `lines->allowance_charges` Grupo de campos para información relacionada con un cargo o un descuento en la línea. Este grupo se debe informar a nivel de ítem, si y solamente si el cargo o descuento afecta la base gravable del ítem. Si un cargo individual, general a la factura genera IVA, debe reportarse como ítem. Para el caso de los descuentos a nivel de ítem no es necesario codificarlos. Este campo es opcional y debe ser un arreglo de objetos. - #### Ejemplo ```json "allowance_charges": [ { "amount": "10000", "base_amount": "725000", "charge_indicator": true, "allowance_charge_reason": "Motivo del cargo a la linea" }, { "amount": "10000", "base_amount": "725000", "charge_indicator": false, "discount_id": 1, "allowance_charge_reason": "Motivo del descuento a la linea" } ] ``` - #### Detalle de los campos - #### `amount` Valor total del cargo o descuento. _Este campo es obligatorio_ y debe ser un número flotante con máximo dos decimales encerrado entre `""`. - #### `base_amount` Valor Base para calcular el descuento el cargo. _Este campo es obligatorio_ y debe ser un número flotante con máximo dos decimales encerrado entre `""`. - #### `charge_indicator` Indica que el elemento es un Cargo y no un descuento. Cargo es `true`, es un Débito aumenta el valor de la item. Descuento es `false`, un Crédito descuenta el valor del ítem El elemento solamente puede identificar una de las informaciones. _Este campo es obligatorio_ cuando el cargo no es un descuento y debe ser un booleano. - #### `allowance_charge_reason` Texto libre para informar de la razón del descuento. _Este campo es obligatorio_ y debe ser un string. ### `lines->tax_totals` Grupo de campos para información relacionada con todos los impuestos de la línea. _Este campo es obligatorio_ solo cuando la línea tiene impuestos y debe ser un arreglo de objetos. La estructura del objeto varía según el **tipo de tributo**: porcentual (ej. IVA) o **nominal/valor fijo por unidad** (ej. Código `18`, INC Bolsas - Código `22`, etc.). - #### Ejemplo (Impuesto Porcentual — IVA, Código `01`) ```json "tax_totals": [ { "tax_id": "1", "tax_amount": 294.633000, "taxable_amount": 1550.700000, "percent": 19 } ] ``` - #### Ejemplo (Impuesto Nominal — Código `32`) :::warning Reglas DIAN para impuestos nominales La DIAN valida **matemáticamente** que `tax_amount = per_unit_amount × base_unit_measure`. Los campos `percent` y `taxable_amount` **deben enviarse con valor `0`** para este tipo de tributo. La regla de validación es `FAX07` (línea) / `FAS07` (cabecera). ::: ```json "tax_totals": [ { "tax_id": "18", "tax_amount": 387.675000, "taxable_amount": 0, "percent": 0, "per_unit_amount": 387.675000, "base_unit_measure": 1, "quantity_units_id": 70 } ] ``` > **Cálculo:** `387.675 × 1 = 387.675` (debe ser matemáticamente exacto). El valor de `quantity_units_id` debe corresponder al código numérico de la unidad de medida en el catálogo DIAN. - #### Detalle de los campos - #### `tax_id` ID del impuesto. _Este campo es obligatorio_ y debe ser un string. - #### `tax_amount` Monto o valor total del impuesto. _Este campo es obligatorio_ y debe ser un número flotante con máximo dos decimales. - Para impuestos porcentuales: resultado de `taxable_amount × percent / 100`. - Para impuestos nominales: resultado exacto de `per_unit_amount × base_unit_measure`. - #### `taxable_amount` Base gravable del impuesto. _Este campo es obligatorio_ y debe ser un número flotante. - Para impuestos porcentuales: valor sobre el que se aplica el porcentaje. - Para impuestos nominales: **debe enviarse como `0`**. - #### `percent` Porcentaje del impuesto. _Este campo es obligatorio_ y debe ser un número. - Para impuestos porcentuales: valor del porcentaje (ej. `19` para IVA del 19%). - Para impuestos nominales: **debe enviarse como `0`**. - #### `per_unit_amount` Valor monetario fijo del tributo por cada unidad de medida física. Obligatorio para impuestos nominales. Debe ser un número flotante con máximo dos decimales. - #### `base_unit_measure` Cantidad de unidades físicas sujetas al impuesto. Obligatorio para impuestos nominales. Debe ser un número flotante. - #### `quantity_units_id` Código numérico de la unidad de medida del catálogo DIAN para `base_unit_measure`. Obligatorio para impuestos nominales. Debe ser un número entero. ### `tax_totals` 🔴 Arreglo que contiene la suma de todos los impuestos del documento, agrupados por tipo de impuesto. _Este campo es obligatorio_ solo cuando el documento tiene impuestos. La estructura del objeto dentro de este arreglo varía según el **tipo de tributo**: porcentual (ej. IVA) o **nominal/valor fijo por unidad** (ej. Código `18`, INC Bolsas - Código `22`, INCarbono, INCombustibles). - #### Ejemplo completo (Porcentual + Nominal) :::warning Reglas DIAN para impuestos nominales La DIAN valida **matemáticamente** que `tax_amount = per_unit_amount × base_unit_measure`. Los campos `percent` y `taxable_amount` **deben enviarse con valor `0`** para impuestos nominales. - Regla a nivel de línea: **`FAX07`** (Factura) / **`DEAX07`** (Doc. Equivalente). - Regla a nivel global: **`FAS07`** (Factura) / **`DEAS07`** (Doc. Equivalente). ::: ```json "tax_totals": [ { "tax_id": "1", "tax_amount": 294.633000, "taxable_amount": 1550.700000, "percent": 19 }, { "tax_id": "18", "tax_amount": 387.675000, "per_unit_amount": 387.675000, "base_unit_measure": 1, "quantity_units_id": 70, "taxable_amount": 0, "percent": 0 } ] ``` > **Cálculo entrada nominal:** `387.675 × 1 = 387.675`. La multiplicación debe ser matemáticamente exacta y consistente con la suma de los valores de línea para no detonar la regla de descuadre general. - #### Detalle de los campos - #### `tax_id` - ID del impuesto. _Este campo es obligatorio_ y debe ser un string. - Este campo agrupa todos los impuestos que tengan el mismo ID. - #### `tax_amount` - Monto o valor total del impuesto. _Este campo es obligatorio_ y debe ser un número flotante con máximo dos decimales. - Este campo agrupa la suma del monto o valor total de todos los impuestos que tengan el mismo ID. - Para impuestos nominales: resultado exacto de `per_unit_amount × base_unit_measure`. - #### `taxable_amount` - Base gravable del impuesto. _Este campo es obligatorio_ y debe ser un número flotante con máximo dos decimales. - Para impuestos porcentuales: valor sobre el que se aplica el porcentaje. - Para impuestos de valor fijo por unidad (**códigos 21, 22, 23, 24, 18** y similares): **debe enviarse como `0`**. - #### `percent` - Porcentaje del impuesto. _Este campo es obligatorio_ y debe ser un número. - Para impuestos porcentuales (ej: IVA - Código `01`): valor del porcentaje (ej. `19`). - Para impuestos de valor fijo por unidad (**códigos 21, 22, 23, 24, 18** y similares): **debe enviarse como `0`**. - ### `quantity_units_id` - Código **numérico** de la unidad de medida base del catálogo DIAN sobre la cual se aplica la tarifa fija del tributo. - Obligatorio para impuestos de valor fijo por unidad (**códigos 21, 22, 23, 24, 18** y similares). - Debe ser un número entero (ej. `886` para NIU - Unidad en INC Bolsas; `821` para la unidad de medida en el código `18`). - ### `per_unit_amount` - Tarifa o valor monetario fijo del tributo por cada unidad de medida física. Obligatorio para impuestos nominales (**códigos 21, 22, 23, 24, 18** y similares). - Debe ser un número flotante con máximo dos decimales (ej. `39.16` para código `18`; `70.00` para INC Bolsas 2025). - ### `base_unit_measure` - Cantidad de unidades físicas sujetas al impuesto. Obligatorio para impuestos nominales (**códigos 21, 22, 23, 24, 18** y similares). - Para INC Bolsas su valor es generalmente `1`; para otros nominales corresponde al total de unidades físicas del ítem (ej. `9.90` para código `18`). - Debe ser un número flotante. ### `additional_document_reference` 🟡 Arreglo de objetos utilizado para referenciar documentos adicionales. **Es OBLIGATORIO** para Facturas de Contingencia (Tipo 03) para relacionar el documento físico entregado. Para otros documentos, es **opcional** y sirve para referenciar documentos comerciales (múltiples órdenes, contratos, etc). **🧾 Campos de la referencia** | Campo | Tipo | Requerido | Descripción | |-------|:----:|:---------:|-------------| | `number` | string | **Sí** | Prefijo y Número del documento referenciado (ej. `"PAPEL-001"`) | | `date` | string | ⚠️ Condicional | Fecha de emisión del documento. **Obligatorio para Tipo 03**. Formato `YYYY-MM-DD` | | `uuid` | string | ⚠️ Condicional | CUFE/CUDE del documento referenciado. **Obligatorio para referencias electrónicas**. _(No aplica para papel)_ | | `code` | string | Opcional | Código interno de la empresa (ej. `"TALONARIO"`, `"OC"`) | | `scheme_name` | string | Opcional | Algoritmo del UUID (ej. `"CUFE-SHA384"`) | **🚨 Uso en Factura de Contingencia (Tipo 03)** Cuando expide una factura física por contingencia, luego debe transmitirla con `type_document_id = 9`. En este caso, informe los datos del papel y **no envíe CUFE**. ```json "additional_document_reference": [ { "number": "TALONARIO-123", "date": "2025-02-10", "code": "TALONARIO" } ] ``` **🏢 Uso para Referencias Comerciales** Sirve para referenciar múltiples órdenes de compra, contratos u otros documentos electrónicos. ```json "additional_document_reference": [ { "number": "OC-2025-001", "uuid": "0bd41b047f40dbca91ab0cdebdb89f6a41b57aa821ca92be68f05a58acbad48f04f66301e2df014965d588734c4ee567", "scheme_name": "CUFE-SHA384", "date": "2025-01-15", "code": "OC" } ] ``` --- ### `additional_data` 🟢 **NEW** Información que quieres ver impresa en el PDF del documento y que **no viaja a la DIAN**: centro de costo, datos del afiliado, orden de servicio, medios de pago internos. :::info ¿Por qué existe esto? El modelo de factura electrónica cubre lo que exige la DIAN. Todo lo demás —lo que tu operación necesita ver en el documento— no tiene sitio en ese modelo. Este campo lo resuelve: envías los datos en el JSON y la plantilla **Preprinted** los imprime sin que haya que modificarla. Las plantillas Classic, Minimal y Modern no imprimen este bloque (ver [Cómo llega al PDF](https://docs.matias-api.com/docs/billing-fields#additional-data-pdf)). ::: #### Los tres ámbitos de datos adicionales | Ámbito | Clave en el JSON | Para qué | |---|---|---| | Documento | `additional_data` | Centro de costo, afiliado, orden de servicio | | Cliente | `customer.extra_data` | Nº de socio, categoría, fecha de vinculación | | Línea | `lines[].extra_data` | Lote, fecha de vencimiento, serial | Ninguno de los tres se envía a la DIAN. **📦 Con secciones — Estructura recomendada** Cada sección es un bloque con título; la plantilla la pinta como tal. ```json "additional_data": [ { "section": "INFORMACIÓN DEL CENTRO DE COSTO", "fields": [ { "title": "Nombre del Centro de Costo", "value": "Sede Norte" }, { "title": "Dirección y Teléfono", "value": "Av. Siempre Viva 742 - 601 123 4567" }, { "title": "Ciudad", "value": "Bogotá D.C." } ] }, { "section": "INFORMACIÓN DEL USUARIO", "fields": [ { "title": "Afiliado Dependiente/Particular", "value": "Juan Pérez Gómez" }, { "title": "Identificación", "value": "1098765432" } ] } ] ``` **📋 Lista plana — Sin secciones** Si solo necesitas un par de campos sueltos, omite las secciones: ```json "additional_data": [ { "title": "Orden de servicio", "value": "OS-2026-0042" }, { "title": "Observación", "value": "Entrega en portería" } ] ``` Se guardan como una sección sin título. :::warning No mezcles secciones y campos sueltos La validación revisa cada elemento por separado: los que traen `fields` (o `items`) son secciones y los demás son campos sueltos, así que un arreglo que mezcla las dos formas **no** se rechaza. El PDF, en cambio, decide la forma de todo el arreglo por el **primer elemento**: - Si el primer elemento no trae `fields` y trae un `value` no nulo, todo el arreglo se lee como lista plana y las secciones se descartan sin aviso. - En cualquier otro caso, todo se lee como secciones y los campos sueltos se descartan. Por eso, en una lista plana el **primer campo debe llevar `value` no nulo** (vale `""`). Si el primero no trae `value` o lo trae en `null`, no se imprime nada del bloque. ::: **📐 Estructura de campos** **Sección:** | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `section` | string | No | Título del bloque (alias: `title`). Omitido o `null` para campos sueltos. Máx. 120 | | `order` | int | No | Orden del bloque. Por defecto, el de llegada | | `fields` | array | **Sí** | Campos del bloque (alias: `items`). Es lo que identifica a un elemento como sección: sin `fields` el elemento se trata como un campo suelto. Un arreglo vacío se acepta y no imprime nada | **Campo:** | Campo | Tipo | Requerido | Por defecto | Descripción | |---|---|---|---|---| | `title` | string | **Sí** | — | Etiqueta (alias: `label`). No puede ir vacía. Máx. 60 | | `value` | string o number | No | vacío | Valor a imprimir. Debe ser un dato simple: un arreglo u objeto se rechaza. `null` u omitido se imprime vacío. Máx. 500 | | `type` | string | No | `TEXT` | `TEXT`, `NUMBER`, `DATE`, `CURRENCY` (alias: `value_type`; acepta minúsculas) | | `align` | string | No | `LEFT` | `LEFT`, `CENTER`, `RIGHT` (alias: `alignment`; acepta minúsculas) | | `order` | int | No | orden de llegada | Orden dentro de la sección | El campo `type` le dice a la plantilla cómo formatear el valor, sin deducirlo del contenido: ```json { "title": "Anticipos", "value": "150000", "type": "CURRENCY", "align": "RIGHT" } ``` Con 2 decimales configurados, se imprime como `$ 150.000,00` alineado a la derecha. Envía el valor **en crudo** (`150000`), no preformateado. Cómo imprime cada `type` la plantilla Preprinted: | `type` | Se imprime | |---|---| | `CURRENCY` | El símbolo de la moneda del documento (por defecto `$`), un espacio y el número con punto de miles, coma decimal y los decimales de totales configurados en la empresa | | `NUMBER` | El número con punto de miles y sin decimales | | `DATE` | El valor tal cual: no se le aplica formato de fecha | | `TEXT` | El valor tal cual | `CURRENCY` y `NUMBER` solo dan formato si `value` es numérico. Si no lo es, se imprime tal cual. `align` fija la alineación del valor dentro de su celda. **⚠️ Validación y límites** Las peticiones que excedan estos límites se rechazan con **HTTP 422**, indicando qué campo lo provocó. | Regla | Límite | |---|---| | Elementos de primer nivel del arreglo (secciones o, en una lista plana, campos) | 10 | | Campos por sección | 20 | | Longitud de `title` | 60 | | Longitud de `value` | 500 | | Título de sección | 120 | El límite de 10 cuenta los elementos de primer nivel de `additional_data`, antes de distinguir secciones de campos. Una **lista plana** admite por tanto como máximo **10 campos**, no 20, y el exceso se rechaza con `El campo additional data admite como máximo 10 secciones.` **Se rechaza con HTTP 422** (en `POST /invoice`, `/notes/credit`, `/notes/debit`, `/ds/document`, `/ds/adjustment-note`, `POST /auto-increment/*` y `PATCH /auto-increment/*/{uuid}`): | Entrada | Mensaje | |---|---| | `additional_data` no es un arreglo | Llegan dos mensajes: `El campo additional_data debe ser un arreglo de secciones o de campos` y `El campo additional data debe ser un arreglo de secciones o de campos.` | | Más de 10 elementos | `El campo additional data admite como máximo 10 secciones.` | | Un elemento no es un objeto (por ejemplo, un texto suelto) | `El elemento 2 de additional data debe ser un objeto.` | | El título de la sección (`section` o `title`) no es texto | `El título de la sección 0 debe ser texto.` | | Título de sección de más de 120 caracteres | `El título de la sección 0 supera los 120 caracteres.` | | `fields` no es un arreglo | `Los campos de la sección 0 deben ser un arreglo.` | | Más de 20 campos en una sección | `La sección 0 admite como máximo 20 campos.` | | Un campo de la sección no es un objeto | `El campo 1 de la sección 0 debe ser un objeto.` | | Campo sin `title` o con `title` vacío | `El campo 0.1 requiere un título.` | | `title` de más de 60 caracteres | `El título del campo 0.1 supera los 60 caracteres.` | | `value` es un arreglo u objeto | `El valor del campo 0.1 debe ser un dato simple.` | | `value` de más de 500 caracteres | `El valor del campo 0.1 supera los 500 caracteres.` | | `type` distinto de `TEXT`, `NUMBER`, `DATE`, `CURRENCY` | `El tipo del campo 0.1 debe ser uno de: TEXT, NUMBER, DATE, CURRENCY.` | | `align` distinto de `LEFT`, `CENTER`, `RIGHT` | `La alineación del campo 0.1 debe ser una de: LEFT, CENTER, RIGHT.` | En los mensajes, el número que acompaña a «elemento» o «sección» es su índice dentro de `additional_data`, y `0.1` es la ruta del campo: sección `0`, campo `1` (en una lista plana es solo el índice del campo). Todos los mensajes llegan en `errors.additional_data`, y la validación del bloque se detiene en el primer error que encuentra. El error llega con el formato estándar de validación: ```json { "message": "El tipo del campo 0.1 debe ser uno de: TEXT, NUMBER, DATE, CURRENCY.", "errors": { "additional_data": [ "El tipo del campo 0.1 debe ser uno de: TEXT, NUMBER, DATE, CURRENCY." ] } } ``` **Casos tolerados** (el documento se emite igual): | Entrada | Comportamiento | |---|---| | `additional_data` ausente, `null` o `[]` | No se imprime ningún bloque | | Sección con `fields` vacío | Se acepta y no imprime nada | | Campo sin `value` o con `value: null` | Se imprime la etiqueta con valor vacío. Excepción: en una lista plana, si es el **primer** campo no se imprime nada del bloque (ver [Lista plana](https://docs.matias-api.com/docs/billing-fields#additional_data-)) | | Arreglo que mezcla secciones y campos sueltos | Se acepta, pero el PDF solo imprime una de las dos formas, según el primer elemento | | `type` o `align` en minúsculas (ej. `"currency"`, `"right"`) | Se aceptan | En el [envío masivo](https://docs.matias-api.com/docs/endpoints/bulk-documents.md) el payload de cada documento no pasa por esta validación: allí un `type` o `align` no reconocido cae al valor por defecto (`TEXT` / `LEFT`), un campo sin `title` se omite y lo que excede los límites se recorta (secciones y campos de más, textos largos) en lugar de rechazarse. :::warning Se rechaza, no se trunca Un dato cortado a la mitad en una factura impresa es peor que un error al emitir. Si un valor supera el límite, la petición falla para que puedas corregirla antes de que el documento llegue a la DIAN. ::: **🖨️ Cómo llega al PDF — Plantilla Preprinted** :::caution Solo la plantilla Preprinted imprime additional_data Las plantillas **Classic**, **Minimal** y **Modern** no leen el bloque. La API lo acepta y lo valida igual en `POST /invoice`, `/notes/credit`, `/notes/debit`, `/ds/document`, `/ds/adjustment-note`, `POST /auto-increment/*` y `PATCH /auto-increment/*/{uuid}`, pero con esas plantillas no aparece en el PDF. La plantilla de la empresa se elige en [Plantillas de la empresa](https://docs.matias-api.com/docs/endpoints/company-templates#datos-adicionales-pdf). ::: Con **Preprinted**, una sección nueva aparece en el PDF sin que nadie toque la plantilla. La plantilla toma los datos del propio documento, así que se imprimen desde la primera emisión. **Secciones con sitio fijo.** Preprinted reconoce dos secciones por su título, sin distinguir mayúsculas, acentos ni espacios, y las coloca en un sitio fijo del formato: | El título de la sección contiene | Dónde se pinta | Etiquetas que se imprimen | |---|---|---| | *"centro de costo"* o *"centro costo"* | Recuadro superior izquierdo | El `title` de cada campo. Si envías menos de tres campos, los renglones que faltan se rotulan `Nombre del Centro de Costo`, `Dirección y Teléfono` y `Ciudad` | | *"del usuario"* o *"afiliado"* | Bloque **INFORMACIÓN DEL USUARIO** | Fijas: `Afiliado Dependiente/Particular`, `Identificación` y `Descripción`. El `title` que envíes se ignora | - Ambos bloques imprimen los campos **por posición** y solo los **3 primeros**: del 4.º en adelante no se imprimen. - Solo se usa la **primera** sección que coincide. Las demás secciones cuyo título también coincide no se imprimen, porque se excluyen del recorrido general. **El resto de secciones** se pinta en el orden en que llegan (o el que fijes con `order`), dentro del cuerpo del documento: después de **INFORMACIÓN DEL CLIENTE** y del bloque del usuario, y **antes** del documento de referencia (en las notas) y del detalle de líneas. Cada sección es una tabla con su título como franja (si la sección no tiene título, no lleva franja) y dos campos por renglón, con la forma `Etiqueta: valor`. **Campo `Anticipos`.** Si un campo de `additional_data` se titula `Anticipos` (sin distinguir mayúsculas ni acentos), Preprinted muestra además su valor en la fila **ANTICIPOS** de los totales. Esa fila es informativa: el **TOTAL A PAGAR** sigue siendo el total del documento. --- ## Ejemplo Mínimo Requerido {#ejemplo-mínimo-requerido} Este es un ejemplo con **SOLO los campos obligatorios** para generar una factura simple (tipo 1): ```json { "resolution_number": "18760000001", "prefix": "FEV", "document_number": 990000001, "operation_type_id": 1, "type_document_id": 1, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "NOMBRE DEL CLIENTE", "dni": "1234564", "email": "correo@cliente.com" }, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "141100.00" } ], "legal_monetary_totals": { "line_extension_amount": "131600.00", "tax_exclusive_amount": "131600.00", "tax_inclusive_amount": "141100.00", "payable_amount": "141100.00" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "131600.00", "description": "Producto ejemplo", "price_amount": "131600.00" } ], "tax_totals": [ { "tax_id": "01", "tax_amount": 9500, "taxable_amount": 131600, "percent": 19 } ] } ``` **📊 Notas importantes sobre este ejemplo:** ✅ Todos los campos 🔴 **Obligatorios** están presentes ✅ Campos 🟢 **Opcionales** se omitieron para simplicidad ✅ Puede extenderse con campos adicionales según necesidad ⚠️ Para contingencia (tipo 03), debe agregar [`additional_document_reference`](https://docs.matias-api.com/docs/billing-fields#additional_document_reference-referencia-a-documento-adicional) ⚠️ Para POS (tipo 9), debe agregar [`point_of_sale`](https://docs.matias-api.com/docs/billing-fields#point_of_sale) --- ## 🎯 Próximos Pasos [📄 **Factura Simple** Ejemplo práctico paso a paso](https://docs.matias-api.com/docs/use-cases/simple-invoice.md) [🌎 **Exportaciones** Facturas internacionales](https://docs.matias-api.com/docs/use-cases/export-scenarios.md) [🔌 **Endpoints API** Referencia completa de endpoints](https://docs.matias-api.com/docs/endpoints) [⚠️ **Errores Comunes** Troubleshooting](https://docs.matias-api.com/docs/use-cases/common-errors.md) --- 📅 **Última actualización:** Febrero 2026 • 📖 **Tipo:** Referencia Técnica Completa • 🎯 **Nivel:** ⭐⭐⭐ Avanzado --- # 📬 Respuestas de la API :::info 📖 Guía Completa de Respuestas JSON Todas las respuestas que emite la API están estructuradas en formato JSON estandarizado, proporcionando información detallada sobre el estado de la solicitud, documentos generados y las validaciones emitidas por la DIAN. ::: ✅ **HTTP 200/201** Procesamiento exitoso ⏳ **StatusCode 98** En proceso / Reintente ❌ **HTTP 4xx/5xx** Errores y Rechazos 🔄 **Contingencia** Timeouts y posible envío --- ## 🚀 Quick Reference | Código | Tipo | Descripción | Acción Recomendada | |---|---|---|---| | **200** | 200 OK | Solicitud procesada. En la emisión, la DIAN pudo **aceptar o rechazar** el documento: revisa `response.IsValid` | `IsValid: "true"` → almacenar archivos. `IsValid: "false"` → corregir según `response.ErrorMessage` | | **201** | 201 Created | Recurso creado o lote aceptado (p. ej. `POST /bulk/documents`, creación de tokens o webhooks). La emisión unitaria **no** responde 201 | Guardar el identificador devuelto (p. ej. `batch_id`) | | **202** | 202 Accepted | Solicitud aceptada para procesarse en segundo plano (p. ej. `POST /events/mailbox/{id}/retry`) | Consultar el estado más tarde | | **400** | 400 Bad Request | Regla de negocio incumplida (documento ya validado, en cola, `send_to_queue=1`, etc.) | Leer `message` y corregir la petición | | **401** | 401 Unauthorized | Sin autorización (falta el token o expiró) | Renovar el token de acceso en la cabecera | | **402** | 402 Payment Required | Saldo insuficiente de folios o cuenta inactiva | Ampliar paquete o renovar suscripción | | **403** | 403 Forbidden | Sin `error_code`: acción no autorizada (p. ej. un `client_uuid` que no es de tu cuenta). Con `error_code: "RECEPTION_NOT_IN_PLAN"`: el plan no incluye la recepción de facturas ni los eventos RADIAN | Sin `error_code`: verificar permisos y `client_uuid`. Con `RECEPTION_NOT_IN_PLAN`: actualizar o renovar el plan (solo planes de pago vigentes) | | **404** | 404 Not Found | Recurso o endpoint no encontrado | Verificar identificador y endpoint | | **409** | 409 Conflict | La operación choca con otra en curso o con el estado del recurso (ver [409](https://docs.matias-api.com/docs/response-json#errores-409)) | Leer `code` / `error_code`; consultar el estado antes de reintentar | | **422** | 422 Unprocessable | Validación del JSON enviado (campos faltantes o con formato inválido). **No** es un rechazo de la DIAN | Revisar el objeto `errors` | | **429** | 429 Too Many Requests | Límite de peticiones superado. Todas las rutas del API (incluido el registro público `POST /register`) tienen un límite general, por defecto de **90 solicitudes por minuto**, contadas por usuario autenticado o, sin token, por IP. Algunas rutas tienen además límites propios más estrictos (creación de tokens, envíos masivos), que pueden responder con su propio mensaje. El límite general responde solo `{"message": "Too Many Attempts."}`, sin `success` | Esperar los segundos del header `Retry-After` y reintentar con backoff | | **500** | 500 Server Error | Error interno. Normalmente trae `reference`. En emisión puede traer `code: "DOCUMENT_POSSIBLY_SENT"` | Con `DOCUMENT_POSSIBLY_SENT`: **no retransmitir**, consultar el estado. Si no, reportar a soporte con `reference` | | **503** | 503 Unavailable | `code: "DIAN_STATUS_UNAVAILABLE"`: no se pudo confirmar en la DIAN el envío anterior de ese número y **no se transmitió** | Reintentar en unos minutos | | **504** | 504 Timeout | Tu cliente o una puerta de enlace cortó la conexión antes de recibir la respuesta | **No retransmitir**: consultar el estado con `GET /status` | :::note La emisión unitaria no responde 201 La emisión es síncrona: responde **HTTP 200** con el resultado de la DIAN. El envío a cola (`send_to_queue: 1`) aún no está habilitado y responde **HTTP 400** `El envío a cola de procesamiento aún no está habilitado.` ::: --- ## 🎯 Guía Rápida de Interpretación :::tip Flujo de Diagnóstico en 4 Pasos 1. **Paso 1: Código HTTP** - `HTTP 200/201` ➔ ✅ La API procesó la solicitud (en la emisión, la DIAN pudo aceptar **o rechazar** el documento) - `HTTP 400–429` ➔ ⚠️ Error en la petición, en los datos o conflicto con otra operación - `HTTP 5xx` ➔ ❌ Error del servidor o de la comunicación con la DIAN (revisa `code` y `reference`) 2. **Paso 2: Propiedad `success`** - `success: true` ➔ ✅ Operación completada - `success: false` ➔ ❌ Fallo en validación, datos o procesamiento 3. **Paso 3: `response.IsValid` y `StatusCode` DIAN** - `IsValid: "true"` y `StatusCode: "00"` ➔ ✅ Autorizado por la DIAN - `IsValid: "false"` ➔ ❌ Rechazado por la DIAN (detalle en `response.ErrorMessage`) - `StatusCode: "98"` ➔ ⏳ En procesamiento en la DIAN 4. **Paso 4: `warnings`** - Arreglo vacío ➔ sin avisos - Con elementos ➔ el documento quedó procesado, pero algo posterior falló o se concilió (ver [Avisos](https://docs.matias-api.com/docs/response-json#warnings)) ::: --- ## 🧱 Formato de los Errores :::tip Envía siempre `Accept: application/json` Sin esa cabecera, un error de validación (422) responde con una redirección (302) y un token inválido o ausente redirige (302) a la página de inicio de sesión en lugar de responder 401. ::: Según el origen del error, el cuerpo tiene una de estas formas: | Origen | HTTP | Cuerpo | |---|---|---| | Validación del JSON enviado | 422 | `message` y `errors` (un objeto con un arreglo de mensajes por cada campo). No incluye `success`. Algunas consultas (p. ej. `GET /documents/consume`) responden además `success: false`, con `message` `Error de validación de datos.` | | Reglas de negocio | 400, 403, 404, 409... | `success: false` y `message`. Los 403 de autorización empiezan con `Acceso no autorizado.` | | Dato que viola una restricción de la base de datos | 400 | `success: false` y `message` con el detalle devuelto por la base de datos. | | Error del servidor | 500 o superior | `success: false`, `message` y `reference`. Los errores internos empiezan con `Error interno del servidor.` | | Autenticación, límite general de peticiones y errores no controlados | 401, 429, 500 | Solo `message` (`Unauthenticated.`, `Too Many Attempts.`, `Server Error`). | **📦 Ejemplos** **Validación (HTTP 422):** ```json { "message": "El campo customer.dni es obligatorio.", "errors": { "customer.dni": ["El campo customer.dni es obligatorio."] } } ``` **Regla de negocio (HTTP 404):** ```json { "success": false, "message": "No se encontró el documento." } ``` **Error del servidor (HTTP 500):** ```json { "success": false, "message": "Error interno del servidor. ...", "reference": "3b0c6f5e-8d2a-4c7e-9f1b-2a6d5e4c3b21" } ``` :::info Campo `reference` en errores 5xx Las respuestas 5xx causadas por un error interno de la emisión y de las consultas traen `reference`: el identificador con el que soporte ubica el error. El 503 `DIAN_STATUS_UNAVAILABLE` no lo trae, porque no es un error interno. **Inclúyelo siempre al reportar un incidente.** Estas respuestas ya no incluyen `file` ni `line`. Un 500 no implica que el error sea de la DIAN. En la emisión, si el error ocurrió **después** de transmitir el documento, la respuesta trae `code: "DOCUMENT_POSSIBLY_SENT"` y no debe reintentarse a ciegas (ver [Timeouts y posible envío](https://docs.matias-api.com/docs/response-json#timeouts-contingencia)). ::: --- ## ✅ Estructura de la Respuesta Exitosa :::tip HTTP 200 — Documento Procesado Exitosamente Cuando la DIAN acepta y autoriza legalmente el documento, la API responde con un objeto que incluye la clave única (CUFE/CUDE/CUNE), el XML firmado, el PDF, el código QR de validación y los avisos (`warnings`) del procesamiento. ::: ### 📋 Campos Principales | Campo | Tipo | Descripción | |---|---|---| | `uuid` | `string` | Identificador del envío en el API. Úsalo con `GET /documents/{uuid}`. | | `message` | `string` | Resumen del resultado del procesamiento. | | `send_to_queue` | `integer` | Siempre `0` (el envío a cola aún no está habilitado). | | `XmlDocumentKey` | `string` | CUFE (Factura) / CUDE (Notas) / CUNE (Nómina). | | `response` | `object` | Respuesta directa de la DIAN. | | `XmlBase64Bytes` | `string` | XML del documento en base64. | | `AttachedDocument` | `object` | Enlaces y metadatos del XML firmado y el contenedor Zip. | | `qr` | `object` | Rutas y base64 para el código QR de validación. | | `pdf` | `object` | Enlaces de descarga y metadatos del archivo PDF generado. | | `warnings` | `array` | 🆕 Avisos del procesamiento. **Siempre está presente**: arreglo vacío si no hay avisos. Ver [Avisos](https://docs.matias-api.com/docs/response-json#warnings). | | `success` | `boolean` | `true` si la operación se completó. | :::note Ambiente de habilitación DIAN Si el software está en el ambiente de **pruebas (habilitación)** de la DIAN, la emisión responde un objeto reducido: `XmlDocumentKey`, `ZipKey`, `response`, `XmlDocumentName`, `ShippingMethod`, `XmlBase64Bytes` y `success` (sin `uuid`, `pdf` ni `warnings`). ::: **📦 Ver JSON Completo de Respuesta Exitosa** ```json title="response.json" { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "response": { "ErrorMessage": { "string": [ "Regla: RUT01, Notificación: La validación del estado del RUT próximamente estará disponible." ] }, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura electrónica LZT2002, ha sido autorizada.", "XmlBase64Bytes": "", "XmlBytes": { "_attributes": { "nil": "true" } }, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "XmlFileName": "fv09010914030002500000095" }, "XmlBase64Bytes": "", "AttachedDocument": { "pathZip": null, "path": "1/ad/ad09010914030002500000041.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000041.xml", "data": "" }, "qr": { "qrDian": "https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=d45f...", "url": "https://sandbox-api.matias-api.com/qr/1/fv09010914030002500000095.png", "path": "1/fv09010914030002500000095.png", "data": "" }, "pdf": { "path": "1/fv09010914030002500000095.pdf", "url": "https://sandbox-api.matias-api.com/pdf/1/fv09010914030002500000095.pdf", "data": "" }, "warnings": [], "success": true } ``` **🔍 Diccionario Detallado de Campos** #### Objeto `response` (Validación Directa DIAN) | Campo | Tipo | Descripción | |---|---|---| | `ErrorMessage` | `object` | Notificaciones DIAN (si el documento es válido) o motivos del rechazo. | | `IsValid` | `string` | Indica si cumple los estándares oficiales (`"true"` o `"false"`). | | `StatusCode` | `string` | Estado interno DIAN (`"00"`: Autorizado). | | `StatusDescription` | `string` | Glosa oficial (ej. `"Procesado Correctamente."`). | | `StatusMessage` | `string` | Detalle específico de la autorización. | | `XmlDocumentKey` | `string` | CUFE / CUDE del documento. | | `XmlFileName` | `string` | Nombre bajo el cual el XML firmado se registra en la DIAN. | #### Objeto `AttachedDocument` (Archivos XML) | Campo | Tipo | Descripción | |---|---|---| | `pathZip` | `null` | **Obsoleto:** siempre `null`. El ZIP ya no se guarda en el servidor; se arma al enviar el correo. | | `path` | `string` | Ruta relativa del XML firmado. | | `url` | `string` | Enlace público para descarga directa del XML firmado. | #### Objeto `pdf` (Representación Gráfica) | Campo | Tipo | Descripción | |---|---|---| | `path` | `string` | Ruta de almacenamiento de la representación gráfica. | | `url` | `string` | Enlace público de descarga del PDF generado. | --- ## 🔔 Avisos de la Respuesta (`warnings`) `warnings` es un arreglo de objetos `code` + `message`. Un aviso **no convierte en error** un documento que la DIAN ya validó: si algo falla después de la aceptación, la emisión sigue siendo exitosa y se informa aquí. | `code` | Cuándo aparece | ¿Reenviar? | |---|---|---| | `PDF_NOT_GENERATED` | El documento fue validado, pero no se pudo generar la representación gráfica. | ❌ No. Genera el PDF con `POST /documents/pdf/{trackId}?regenerate=1`. | | `EMAIL_NOT_SENT` | El documento fue validado, pero no se pudo enviar el correo (`send_email: 1`). | ❌ No. Reenvía el correo con `POST /documents/sendmail/{trackId}`. | | `ATTACHED_DOCUMENT_NOT_GENERATED` | El documento fue validado, pero no se pudo generar el AttachedDocument (ni enviar el correo, si se pidió). | ❌ No. | | `ATTACHMENTS_NOT_LINKED` | El documento fue procesado, pero no se pudieron vincular sus adjuntos. | ❌ No, si el documento es válido. | | `ALREADY_AUTHORIZED_RECOVERED` | La DIAN ya había autorizado ese número con el mismo contenido: se devuelve el documento autorizado y no se generó uno nuevo. | ❌ No: el documento es válido. | | `ALREADY_AUTHORIZED_OTHER_CONTENT` | La DIAN ya autorizó ese número con **otro contenido**. El documento enviado no se marcó como válido. | ❌ No con ese número: consulta el documento autorizado. | | `ALREADY_PROCESSED_NOT_CONFIRMED` | La DIAN indicó que el documento fue procesado anteriormente, pero no confirmó su autorización. | ⏳ Consulta su estado antes de reenviarlo. | Los tres avisos `ALREADY_*` solo aparecen en producción (ver [Documento duplicado y regla 90](https://docs.matias-api.com/docs/response-json#documento-duplicado)). **📦 Ejemplo: correo no enviado (`EMAIL_NOT_SENT`)** ```json { "warnings": [ { "code": "EMAIL_NOT_SENT", "message": "El documento fue validado por la DIAN, pero no se pudo enviar el correo. Motivo: No se pudo enviar el email a ningún destinatario. Errores: El envío fue bloqueado automáticamente porque la(s) siguiente(s) dirección(es) no puede(n) recibir correo. Verifique que estén bien escritas: compras@dominio-inexistente.com (el dominio dominio-inexistente.com no existe o no recibe correo) No reenvíe el documento: ya es válido ante la DIAN." } ] } ``` **Sobre `EMAIL_NOT_SENT`:** - El texto `Motivo:` solo se incluye cuando la causa es de negocio, por ejemplo: ningún destinatario pudo recibir el correo (formato o dominio inválido, envío reciente o límite de envíos), ya hay un envío de correo en curso para ese documento o se excedió el límite de destinatarios. Los errores técnicos no se detallan. - Si **solo algunos** destinatarios fallan, el correo se envía a los demás y **no** se agrega el aviso. --- ## 🔄 Documento Duplicado y Regla 90 :::warning Documento ya validado — HTTP 400 Si intentas emitir de nuevo un prefijo y consecutivo que ya fue validado ante la DIAN, la API responde **HTTP 400** con el registro del envío existente en `data`: ::: ```json title="response.json" { "message": "El documento (Factura electrónica) con numero LZT224 ya se encuentra validado.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT224", "XmlDocumentKey": "cf9864294501e8a9578235dd2ab3c4fd...", "is_valid": 1 }, "success": false } ``` Si ese número está en cola de procesamiento, la respuesta es también **HTTP 400**: `El documento (Factura electrónica) con numero LZT224 se encuentra en cola de procesamiento.`, con `data`. **Regla 90 ("Documento procesado anteriormente")** — en producción, si la DIAN responde que el documento ya fue procesado, la API lo concilia con la DIAN: - Si la DIAN autorizó ese número con el mismo contenido, devuelve el documento autorizado con el aviso `ALREADY_AUTHORIZED_RECOVERED`. - Si no, devuelve el rechazo con el aviso `ALREADY_AUTHORIZED_OTHER_CONTENT` o `ALREADY_PROCESSED_NOT_CONFIRMED`. - Si en ese momento no se pudo consultar la DIAN, el rechazo llega tal cual, sin aviso: consulta el estado antes de reenviar. - Nunca guarda el intento rechazado encima de un documento ya validado. --- ## ⚠️ Rechazo de la DIAN (HTTP 200) :::danger Rechazo de Validación DIAN Cuando el documento contiene errores aritméticos, de catálogos o de reglas de validación DIAN, la API responde **HTTP 200** con `success: true` y `response.IsValid: "false"`. El código HTTP 422 se reserva para la validación del JSON enviado, antes de llegar a la DIAN. ::: ```json title="response.json" { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "response": { "IsValid": "false", "StatusCode": "99", "StatusDescription": "Documento con errores en campos obligatorios.", "ErrorMessage": { "string": [ "Regla: FAS01A, Notificación: Error en el cálculo de la base imponible del IVA." ] } }, "XmlBase64Bytes": "", "AttachedDocument": null, "qr": null, "pdf": null, "warnings": [], "success": true } ``` --- ## 🚦 Conflictos (HTTP 409) | `code` / `error_code` | Endpoint | Significado y acción | |---|---|---| | `DOCUMENT_IN_TRANSMISSION` | Emisión, `POST /status/document/{trackId}`, `DELETE /documents/{id}` | Ese número se está transmitiendo a la DIAN en otra solicitud. No envíes en paralelo: espera y consulta el estado. | | `DOCUMENT_IN_VALIDATION` | Emisión (producción) | La DIAN aún valida (`StatusCode 98`) el envío anterior de ese número. `data.document_key` trae su CUFE/CUDE. No se transmitió: consulta el estado en unos minutos. | | — | Emisión | `El uuid proporcionado ya se usó en otro documento de la empresa. No se emitió de nuevo: consulte ese documento o envíe un uuid nuevo.` | | — | Envío de eventos RADIAN | `El evento ya se está enviando.` | | — | Importación de recepción por CUFE/CUDE | `El cufe/cude ya está en proceso de importación.`, el CUFE/CUDE ya fue importado y omitido, o el documento fue eliminado de la recepción (impórtalo de nuevo por su CUFE). | | `MAILBOX_RETRY_UNAVAILABLE`, `MAILBOX_RETRY_NO_LOCATION`, `MAILBOX_RETRY_EXPIRED`, `MAILBOX_RETRY_IN_PROGRESS`, `MAILBOX_ALREADY_PROCESSED` | `POST /events/mailbox/{id}/retry` | El correo del buzón no se puede reintentar (ver `message`). | | — | Personas de recepción | No se puede eliminar la persona por defecto mientras haya otras, ni cambiar el modo de envío sin personas registradas. | **📦 Ejemplos de 409 en la emisión** ```json { "code": "DOCUMENT_IN_TRANSMISSION", "message": "El documento LZT836 ya se está transmitiendo a la DIAN en otra solicitud; consulte su estado antes de enviarlo de nuevo.", "data": { "document_number": "LZT836" }, "success": false } ``` ```json { "code": "DOCUMENT_IN_VALIDATION", "message": "La DIAN aún está validando el documento LZT836 (CUFE/CUDE cf9864294501e8a9...). No se transmitió de nuevo: consulte su estado en unos minutos antes de reenviarlo.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "document_key": "cf9864294501e8a9..." }, "success": false } ``` --- ## ⏳ Timeouts y Posible Envío :::info `DOCUMENT_POSSIBLY_SENT` — No retransmitir a ciegas Si la comunicación con la DIAN falla **después** de transmitir el documento (timeout, respuesta inesperada o fallo al guardar), la API no puede saber si la DIAN lo recibió. Responde normalmente con **HTTP 500**, `code: "DOCUMENT_POSSIBLY_SENT"`, `data.uuid`, `data.document_number` y `reference`. El consecutivo se conserva. 1. **No emitas de nuevo** el documento con otro número. 2. **Consulta el estado** con `GET /documents/{uuid}` (usando `data.uuid`) o con `GET /status?prefix=...&number=...`. 3. **Contingencia Tipo 04:** si la caída persiste y es informada oficialmente por la DIAN, emite con `operation_type_id: 3` (Factura de Contingencia) y transmite dentro de las 48 horas siguientes. ::: ```json title="response.json" { "success": false, "message": "No se obtuvo una respuesta definitiva para el documento LZT836 y es posible que la DIAN lo haya recibido. No lo emita de nuevo: consulte su estado con el uuid del envío antes de cualquier reintento. Detalle: Error interno del servidor. ...", "reference": "3b0c6f5e-8d2a-4c7e-9f1b-2a6d5e4c3b21", "code": "DOCUMENT_POSSIBLY_SENT", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836" } } ``` :::warning 503 `DIAN_STATUS_UNAVAILABLE` no es una caída general de la DIAN En producción, antes de retransmitir un número cuyo envío anterior no quedó confirmado, la API consulta ese envío en la DIAN. Si no logra confirmarlo, responde **HTTP 503** con `code: "DIAN_STATUS_UNAVAILABLE"` y **no transmite** el documento para no duplicarlo. Reintenta en unos minutos. ::: ```json title="response.json" { "success": false, "code": "DIAN_STATUS_UNAVAILABLE", "message": "No se pudo confirmar en la DIAN el estado del envío anterior del documento LZT836 (CUFE/CUDE cf9864294501e8a9...). No se transmitió de nuevo para no duplicarlo: intente en unos minutos.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT836", "document_key": "cf9864294501e8a9..." } } ``` --- ## ⏳ StatusCode 98 — En Procesamiento :::tip Estado Transitorio de Validación `StatusCode 98` indica que la DIAN recibió el documento y lo tiene en cola. **No es un error**. 1. Espera 3 a 5 minutos. 2. Consulta el estado con `POST /status/document/{trackId}`. 3. No retransmitas con un nuevo número para evitar cobros dobles o duplicados. En producción, si envías de nuevo el mismo número, la API primero consulta la DIAN: si sigue en 98, responde **HTTP 409** `DOCUMENT_IN_VALIDATION` sin transmitir. ::: ```json title="response.json" { "message": "Solicitud procesada por la DIAN.", "response": { "IsValid": "false", "StatusCode": "98", "StatusDescription": "En Proceso", "XmlDocumentKey": null } } ``` --- ## ❓ Preguntas Frecuentes (FAQ) **¿Qué diferencia hay entre `success` e `IsValid`?** `success: true` indica que la API procesó la solicitud HTTP correctamente. `IsValid: "true"` es la confirmación oficial de que la DIAN autorizó fiscalmente el documento. Un rechazo de la DIAN llega con `success: true` e `IsValid: "false"`. **Recibí un timeout o un `DOCUMENT_POSSIBLY_SENT`, ¿el documento se emitió?** Es posible. El XML pudo llegar a la DIAN y procesarse. **Nunca retransmitas** sin antes consultar el estado con `GET /documents/{uuid}` o `GET /status`. **La respuesta trae `warnings`, ¿debo reenviar el documento?** No. Si `response.IsValid` es `"true"`, el documento ya es válido ante la DIAN; los avisos indican pasos posteriores (PDF, correo, adjuntos) que puedes repetir por separado. **¿Qué significa `nil="true"` en algunos nodos XML?** Es un estándar de serialización SOAP/XML que indica que el campo está vacío (equivalente a `null`). --- 📅 **Versión:** 3.7.0 (Agosto 2026) • 📨 **Respuestas Documentadas:** HTTP, avisos y conflictos • 🎯 **Nivel:** ⭐⭐⭐ Referencia Técnica de Integración --- # Factura A continuación se muestra un ejemplo de un JSON que representa una factura. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "2002", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "attachments": [ { "filename": "Factura.pdf", "content": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIK...[contenido base64 PDF truncado para el ejemplo]...==", "content_type": "application/pdf" } ], "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Factura a crédito A continuación se muestra un ejemplo de un JSON que representa una factura a crédito. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="credit-invoice.json" { "resolution_number": "18764055792775", "date": "2024-01-22", "time": "22:46:53", "notes": "Nota del documento", "document_number": "8003", "operation_type_id": 1, "type_document_id": 7, "graphic_representation": 1, "send_email": 1, "payments": [{ "payment_method_id": 2, "means_payment_id": 30, "value_paid": "224.00", "payment_due_date": "2024-02-22" }], "customer": { "country_id": "170", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Factura con consumidor final A continuación se muestra un ejemplo de un JSON que representa una factura para consumidor final, **Solo se omite el objeto customer**. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice.json" { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "859", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Factura con Impuesto al Consumo de Bolsa Plástica A continuación se muestra un ejemplo de un JSON que representa una factura con regalo. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-gift-tax.json" { "resolution_number":"18764074347312", "prefix":"LZT", "document_number":"2054", "graphic_representation":0, "send_email":0, "operation_type_id":1, "type_document_id":7, "payments":[ { "payment_method_id":1, "means_payment_id":10, "value_paid":"24330.00" } ], "customer":{ "country_id":"", "city_id":"823", "identity_document_id":"1", "type_organization_id":2, "tax_regime_id":1, "tax_level_id":"5", "company_name":"CUANTIAS MENORES", "dni":"222222222", "mobile":"1243436", "email":"cliente6@ejemplo.com", "address":"DG 13 A 33 LC 1", "postal_code":"000000" }, "lines":[ { "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"403.36", "free_of_charge_indicator":false, "description":"BOLSA T-40", "code":"022555", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"403.36", "base_quantity":"1", "tax_totals":[ { "tax_id":"1", "tax_amount":76.64, "taxable_amount":403.36, "percent":19 }, { "tax_id":"10", "tax_amount": 70, "taxable_amount": 0, "quantity_units_id": "886", "per_unit_amount": 70, "base_unit_measure": 1 } ] } ,{ "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"2260.50", "free_of_charge_indicator":false, "description":"SAN LIMPIO BICARBONATO X 1000 ML", "code":"3005964", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"2260.50", "base_quantity":"1", "tax_totals":[ { "tax_id":"1", "tax_amount":429.50, "taxable_amount":2260.50, "percent":19 } ] } ,{ "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"3823.53", "free_of_charge_indicator":false, "description":"FUROR ESPONJA ORO PLATA PG 2 LLV 3", "code":"771952", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"3823.53", "base_quantity":"1", "tax_totals":[ { "tax_id":"1", "tax_amount":726.47, "taxable_amount":3823.53, "percent":19 } ] } ,{ "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"3067.23", "free_of_charge_indicator":false, "description":"GUANTE FUROR DOMESTICO AMARILLO TALLA 7-71/2", "code":"3003670", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"3067.23", "base_quantity":"1", "tax_totals":[ { "tax_id":"1", "tax_amount":582.77, "taxable_amount":3067.23, "percent":19 } ] } ,{ "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"4411.76", "free_of_charge_indicator":false, "description":"SANPIC VAINILLA DOY PACK X 450 ML", "code":"3003014", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"4411.76", "base_quantity":"1", "tax_totals":[ { "tax_id":"1", "tax_amount":838.24, "taxable_amount":4411.76, "percent":19 } ] } ,{ "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"4949.58", "free_of_charge_indicator":false, "description":"PAÑO LIMPION TASK ANTIBACTERIAL PG 4 LLV 6 UND", "code":"3002766", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"4949.58", "base_quantity":"1", "tax_totals":[ { "tax_id":"1", "tax_amount":940.42, "taxable_amount":4949.58, "percent":19 } ] } ,{ "invoiced_quantity":"1.8000", "quantity_units_id":"1093", "line_extension_amount":"1512.61", "free_of_charge_indicator":false, "description":"SALTINAS DORE X 117 G", "code":"3007666", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"840.34", "base_quantity":"1.8000", "tax_totals":[ { "tax_id":"1", "tax_amount":287.40, "taxable_amount":1512.61, "percent":19 } ] } ], "legal_monetary_totals":{ "line_extension_amount":"20428.57", "tax_exclusive_amount":"20428.57", "tax_inclusive_amount":"24380.01", "total_charges":"0.00", "total_allowance":"0", "payable_amount":"24380.01" } , "tax_totals":[ { "tax_id":"1", "tax_amount":3881.42, "taxable_amount":20428.57, "percent":19.00 }, { "tax_id":"10", "tax_amount": 70, "quantity_units_id": "886", "per_unit_amount": 70, "base_unit_measure": 1 } ] } ``` --- # Factura con varios impuestos A continuación se muestra un ejemplo de un JSON que representa una factura con varios impuestos. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-taxes.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "858", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "1000.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "840.34", "free_of_charge_indicator": false, "description": "SERVICIO DE ASEO", "code": "012910", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "840.34", "base_quantity": "1", "tax_totals": [ { "tax_id": "1", "tax_amount": 159.66, "taxable_amount": 840.34, "percent": 19 } ], "mu": "U" } ], "legal_monetary_totals": { "line_extension_amount": "840.34", "tax_exclusive_amount": "840.34", "tax_inclusive_amount": "1000.00", "payable_amount": 1000.00 }, "tax_totals": [ { "tax_id": 6, "tax_amount": 25.21, "taxable_amount": 840.34, "percent": 3 }, { "tax_id": 5, "tax_amount": 23.95, "taxable_amount": 159.66, "percent": 15 }, { "tax_id": 1, "tax_amount": 159.66, "taxable_amount": 840.34, "percent": 19 } ] } ``` --- # Factura con Cargos A continuación se muestra un ejemplo de un JSON que representa una factura con cargos. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-charges.json" { "resolution_number": "18764055792775", "date": "2024-01-22", "time": "22:46:53", "notes": "Nota del documento", "document_number": "8007", "operation_type_id": 1, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "319600.00" }], "allowance_charges": [ { "amount": "10000", "base_amount": "725000", "charge_indicator": true, "allowance_charge_reason": "Motivo del cargo a la factura" }, { "amount": "10000", "base_amount": "725000", "charge_indicator": false, "discount_id": 2, "allowance_charge_reason": "Motivo del descuento a la factura" } ], "customer": { "country_id": "170", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "mobile": "3108435423", "email": "cliente2@ejemplo.com", "address": "Direccion residencial" }, "lines": [ { "invoiced_quantity": "3", "quantity_units_id": "1093", "line_extension_amount": "81600", "free_of_charge_indicator": false, "description": "Hunters Mini Tumaco 82%", "code": "HMT82", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "27200", "base_quantity": "3", "allowance_charges": [ { "amount": "10000", "base_amount": "725000", "charge_indicator": true, "allowance_charge_reason": "Motivo del cargo a la linea" }, { "amount": "10000", "base_amount": "725000", "charge_indicator": false, "discount_id": 1, "allowance_charge_reason": "Motivo del descuento a la linea" } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100000", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "59500", "base_quantity": "1", "tax_totals": [ { "tax_id": "1", "tax_amount": 19000, "taxable_amount": 100000, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100000", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50000", "base_quantity": "1", "tax_totals": [ { "tax_id": "1", "tax_amount": 19000, "taxable_amount": 100000, "percent": 19 } ] } ], "legal_monetary_totals": { "line_extension_amount": "281600.00", "tax_exclusive_amount": "200000", "tax_inclusive_amount": "319600.00", "payable_amount": "319600.00" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 38000, "taxable_amount": 200000, "percent": 19 } ] } ``` --- # Factura con Descuento A continuación se muestra un ejemplo de un JSON que representa una factura con cargos. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-discuount.json" { "resolution_number": "18764074347312", "date": "2024-10-04", "time": "11:53:23", "notes": "", "document_number": "5045", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "198.00" } ], "customer": { "country_id": "45", "city_id": "1041", "identity_document_id": "3", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "CONSUMIDOR FINAL", "dni": "222222222222", "mobile": "3043965204", "email": "cliente7@ejemplo.com", "address": "CALLE 22 NRO. 32 29", "postal_code": "76834" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "198", "free_of_charge_indicator": false, "description": "CALZADO OSIRIS D3 2001", "code": "A50824", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "220", "base_quantity": "1", "allowance_charges": [ { "amount": "22", "base_amount": "198", "charge_indicator": false, "allowance_charge_reason": "Promocion" } ] } ], "legal_monetary_totals": { "line_extension_amount": "198.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "198.00", "payable_amount": 198 } } ``` --- # Factura con cargos + IVA - descuentos A continuación se muestra un ejemplo de un JSON que representa una factura con cargos + IVA - descuentos. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-taxes-charges-discounts.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "2005", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "159800.00" }], "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente4@ejemplo.com", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "43000.00", "free_of_charge_indicator": false, "description": "PE ROLLO PAPEL OSMOTICO (200MT X 30CM)", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "21800", "base_quantity": "2", "um": "M", "allowance_charges": [ { "amount": "600", "base_amount": "43600", "charge_indicator": false, "discount_id": 1, "allowance_charge_reason": "Motivo del descuento a la linea" } ], "tax_totals": [ { "tax_id": "1", "tax_amount": 8170, "taxable_amount": 43000, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "87000.00", "free_of_charge_indicator": false, "description": "PE ROLLO PAPEL OSMOTICO (500MT X 30CM)", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "43300", "base_quantity": "2", "allowance_charges": [ { "amount": "400", "base_amount": "86600", "charge_indicator": true, "allowance_charge_reason": "Motivo del cargo a la linea" } ], "tax_totals": [ { "tax_id": "1", "tax_amount": 16530, "taxable_amount": 87000, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "5100", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT835", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "2650", "base_quantity": "2", "allowance_charges": [ { "amount": "200", "base_amount": "5300", "charge_indicator": false, "discount_id": 1, "allowance_charge_reason": "Motivo del descuento a la linea" } ] } ], "legal_monetary_totals": { "line_extension_amount": "135100.00", "tax_exclusive_amount": "130000", "tax_inclusive_amount": "159800.00", "payable_amount": "159800.00" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 24700, "taxable_amount": 130000, "percent": 19 } ] } ``` --- # Factura con Propina A continuación se muestra un ejemplo de un JSON que representa una factura con cargos. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-tip.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "1", "graphic_representation": 0, "send_email": 0, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "13600.00" }, { "payment_method_id": 1, "means_payment_id": 47, "value_paid": "10000" } ], "customer": { "country_id": "45", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LEWIS LOPEZ GOMEZ", "dni": "1063279307", "mobile": "3108435431", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 12500, "free_of_charge_indicator": false, "description": "Hamburguesa Gaucha (Hamburguesa Gaucha)", "code": "P118076", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 15000, "base_quantity": 1, "mu": "U", "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento 10%", "base_amount": 15000, "amount": 1500 } ], "tax_totals": [ { "tax_id": "4", "tax_amount": 1000, "percent": 8, "taxable_amount": 12500 } ] }, { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 3333.33, "free_of_charge_indicator": false, "description": "Tocineta", "code": "M58978", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 4000, "base_quantity": 1, "mu": "U", "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento 10%", "base_amount": 4000, "amount": 400 } ], "tax_totals": [ { "tax_id": "4", "tax_amount": 266.67, "percent": 8, "taxable_amount": 3333.33 } ] }, { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 4166.67, "free_of_charge_indicator": false, "description": "Queso Provolone", "code": "M58979", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 5000, "base_quantity": 1, "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento 10%", "base_amount": 5000, "amount": 500 } ], "mu": "U", "tax_totals": [ { "tax_id": "4", "tax_amount": 333.33, "percent": 8, "taxable_amount": 4166.67 } ] } ], "allowance_charges": [ { "charge_indicator": true, "amount": 2000, "base_amount": 20000, "allowance_charge_reason": "PROPINA 10%", "multiplier_factor_numeric": "10.00" } ], "legal_monetary_totals": { "line_extension_amount": 20000, "tax_exclusive_amount": 20000, "tax_inclusive_amount": 21600, "charge_total_amount": 2000.00, "allowance_total_amount": 0, "pre_paid_amount": 0, "payable_amount": 23600 }, "tax_totals": [ { "tax_id": 4, "tax_amount": 1600, "taxable_amount": 20000, "percent": 8 } ] } ``` --- # Factura con Propina con IVA A continuación se muestra un ejemplo de un JSON que representa una factura con cargos. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-tip-tax.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "2052", "graphic_representation": 0, "send_email": 0, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "13600.00" }, { "payment_method_id": 1, "means_payment_id": 47, "value_paid": "10000" } ], "customer": { "country_id": "45", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LEWIS LOPEZ GOMEZ", "dni": "1063279307", "mobile": "3108435431", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 12500, "free_of_charge_indicator": false, "description": "Hamburguesa Gaucha (Hamburguesa Gaucha)", "code": "P118076", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 15000, "base_quantity": 1, "mu": "U", "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento 10%", "base_amount": 15000, "amount": 1500 } ], "tax_totals": [ { "tax_id": "4", "tax_amount": 1000, "percent": 8, "taxable_amount": 12500 } ] }, { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 3333.33, "free_of_charge_indicator": false, "description": "Tocineta", "code": "M58978", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 4000, "base_quantity": 1, "mu": "U", "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento 10%", "base_amount": 4000, "amount": 400 } ], "tax_totals": [ { "tax_id": "4", "tax_amount": 266.67, "percent": 8, "taxable_amount": 3333.33 } ] }, { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 4166.67, "free_of_charge_indicator": false, "description": "Queso Provolone", "code": "M58979", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 5000, "base_quantity": 1, "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento 10%", "base_amount": 5000, "amount": 500 } ], "mu": "U", "tax_totals": [ { "tax_id": "4", "tax_amount": 333.33, "percent": 8, "taxable_amount": 4166.67 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] } ], "allowance_charges": [ { "charge_indicator": true, "amount": 2000, "base_amount": 20000, "allowance_charge_reason": "PROPINA 10%", "multiplier_factor_numeric": "10.00" } ], "legal_monetary_totals": { "line_extension_amount": 20100, "tax_exclusive_amount": 20100, "tax_inclusive_amount": 21719.00, "total_charges": 2000.00, "total_allowance": 0, "pre_paid_amount": 0, "payable_amount": 23719.00 }, "tax_totals": [ { "tax_id": 4, "tax_amount": 1600, "taxable_amount": 20000, "percent": 8 }, { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] } ``` --- # Factura con datos extra A continuación se muestra un ejemplo de un JSON que representa una factura. Este JSON se puede utilizar para pruebas o para simular una factura real. El ejemplo trae los tres ámbitos de datos adicionales, ninguno de los cuales viaja a la DIAN: | Ámbito | Clave en el JSON | En el ejemplo | |---|---|---| | Documento | `additional_data` | Una sección de centro de costo con 3 campos y una sección sin título con un anticipo (`CURRENCY`) y una orden de servicio | | Cliente | `customer.extra_data` | Nº de socio y fecha de vinculación | | Línea | `lines[].extra_data` | Lote y fecha de expiración | :::caution additional_data solo se imprime con la plantilla Preprinted Las plantillas Classic, Minimal y Modern aceptan y validan el bloque, pero no lo imprimen en el PDF. Con Preprinted, la sección cuyo título contiene "centro de costo" va al recuadro superior izquierdo y la sección sin título se imprime en el cuerpo del documento, antes del detalle. El campo `Anticipos` también aparece en la fila ANTICIPOS de los totales, solo como información. Ver [`additional_data`](https://docs.matias-api.com/docs/billing-fields#additional_data-). ::: ```json title="invoice-extra-data.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "4068", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "city_id": "836", "identity_document_id": "3", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002", "extra_data": [ { "title": "No. Socio", "value": "78-54121-454" }, { "title": "FECHA DE VINCULACIÓN", "value": "02/02/2026" } ] }, "additional_data": [ { "section": "INFORMACIÓN DEL CENTRO DE COSTO", "fields": [ { "title": "Nombre del Centro de Costo", "value": "Sede Norte" }, { "title": "Dirección y Teléfono", "value": "Av. Siempre Viva 742 - 601 123 4567" }, { "title": "Ciudad", "value": "Bogotá D.C." } ] }, { "fields": [ { "title": "Anticipos", "value": "150000", "type": "CURRENCY", "align": "RIGHT" }, { "title": "Orden de servicio", "value": "OS-2026-0042" } ] } ], "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "extra_data": [ { "title": "LOTE", "value": "45413", "align": "left" }, { "title": "FECHA DE EXPIRACIÓN", "value": "02/02/2026", "align": "center" } ], "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "extra_data": [ { "title": "LOTE", "value": "26545" }, { "title": "FECHA DE EXPIRACIÓN", "value": "02/04/2028" } ], "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Factura con Retenciones A continuación se muestra un ejemplo de un JSON que representa una factura con retenciones. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-retentions.json" { "resolution_number": "18764055792775", "date": "2024-01-12", "time": "22:46:53", "notes": "Nota del documento", "document_number": "8001", "operation_type_id": 1, "type_document_id": 7, "graphic_representation": 1, "send_email": 0, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "customer": { "country_id": "49", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "NOMBRE DEL CLIENTE", "dni": "123456", "mobile": "310548121", "email": "cliente8@ejemplo.com", "address": "Direccion residencial", "postal_code": "000000" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "6", "tax_amount": 3, "taxable_amount": 100, "percent": 3 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": "224.00" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 }, { "tax_id": "5", "tax_amount": 3.6, "taxable_amount": 24, "percent": 15 }, { "tax_id": "7", "tax_amount": 2.2, "taxable_amount": 200, "percent": 11 } ] } ``` --- # Factura AIU A continuación se muestra un ejemplo de un JSON que representa una factura AIU. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="AIU-invoice.json" { "resolution_number": "18760000001", "date": "2021-11-22", "time": "22:46:53", "notes": "Contrato de servicios AIU por conceptode: El contribuyente debe incluir el objeto del contrato facturado.", "document_number": 990000337, "operation_type_id": 2, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "731400.00" }], "customer": { "country_id": "170", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "email": "cliente2@ejemplo.com", "address": "Direccion residencial" }, "legal_monetary_totals": { "line_extension_amount": "720000.00", "tax_exclusive_amount": "60000", "tax_inclusive_amount": "731400.00", "payable_amount": "731400.00" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "600000", "free_of_charge_indicator": false, "description": "ADMINISTRACIÓN", "notes": "Contrato de servicios AIU por concepto de: Informacion del contrato", "code": "HMT82", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "600000", "base_quantity": "1" }, { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "60000", "free_of_charge_indicator": false, "description": "IMPREVISTO", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "60000", "base_quantity": "1" }, { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "60000", "free_of_charge_indicator": false, "description": "UTILIDAD", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "71400", "base_quantity": "1", "tax_totals": [ { "tax_id": "1", "tax_amount": 11400, "taxable_amount": 60000, "percent": 19 } ] } ], "tax_totals": [ { "tax_id": "1", "tax_amount": 11400, "taxable_amount": 60000, "percent": 19 } ] } ``` --- # Factura partida arancelaria A continuación se muestra un ejemplo de un JSON que representa una factura. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-tariff.json" { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "2056", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" } ], "delivery_terms": { "terms" : "Portes Pagados", "delivery_id": 1 }, "customer": { "country_id": "239", "identity_document_id": "10", "type_organization_id": 1, "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "224.00", "free_of_charge_indicator": false, "description": "CABAÑA BREMEN", "code": "000001", "type_item_identifications_id": "3", "reference_price_id": "1", "price_amount": "224", "base_quantity": "1", "um": "M" } ], "legal_monetary_totals": { "line_extension_amount": "224.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "224.00", "payable_amount": 224 } } ``` --- # Factura con Item de Regalo A continuación se muestra un ejemplo de un JSON que representa una factura con regalo. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-gift.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "", "document_number": "851", "graphic_representation": 1, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments":[ { "payment_method_id":2, "means_payment_id":31, "value_paid":"10210000.00" ,"payment_due_date":"2025-02-27" } ], "customer":{ "country_id":"", "city_id":"836", "identity_document_id":"1", "type_organization_id":2, "tax_regime_id":2, "tax_level_id":"", "company_name":"MARIA BLANDON", "dni":"34044613", "mobile":"3156516023", "email":"DIEGOGO2@GMAIL.COM", "address":"CALLE 20 22 31", "postal_code":"000000" }, "lines":[ { "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"10210000.00", "free_of_charge_indicator":false, "description":"THER IV 100 MSC", "code":"000001", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"10210000", "base_quantity":"1" } ,{ "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"0.00", "free_of_charge_indicator": true, "description":"THER VES IV -OBS", "code":"000017", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"1000", "base_quantity":"1" } ], "legal_monetary_totals":{ "line_extension_amount":"10210000.00", "tax_exclusive_amount":"0.00", "tax_inclusive_amount":"10210000.00", "total_charges":"0.00", "total_allowance":"0", "payable_amount":"10210000.00" } } ``` --- # Factura con Item de Regalo con impuesto A continuación se muestra un ejemplo de un JSON que representa una factura con regalo. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-with-gift-tax.json" { "resolution_number":"18764074347312", "prefix":"LZT", "document_number":"2047", "graphic_representation":0, "send_email":0, "operation_type_id":1, "type_document_id":7, "payments":[ { "payment_method_id":1, "means_payment_id":1, "value_paid":"952.00" } ], "lines":[ { "invoiced_quantity":"1", "quantity_units_id":"1093", "line_extension_amount":"0.00", "free_of_charge_indicator":true, "description":"POSTRE DE LA CASA", "code":"0145", "type_item_identifications_id":"4", "reference_price_id":"1", "price_amount":"11900", "base_quantity":"1", "tax_totals":[ { "tax_id":"4", "tax_amount": 952, "taxable_amount": 11900, "percent": 8 } ] } ], "legal_monetary_totals":{ "line_extension_amount":"0", "tax_exclusive_amount":"11900", "tax_inclusive_amount":"952", "total_charges":"0.00", "total_allowance":"0", "payable_amount":"952" }, "tax_totals": [ { "tax_id": "4", "tax_amount": 952, "taxable_amount": 11900, "percent": 8 } ] } ``` --- # Factura en USD A continuación se muestra un ejemplo de un JSON que representa una factura en dólares. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice_usd.json" { "resolution_number":"18764074347312", "prefix":"LZT", "document_number":"2055", "operation_type_id": 1, "type_document_id": 7, "currency_id": 272, "payments": [ { "payment_method_id": 1, "means_payment_id": 42, "value_paid": "4243.80" } ], "payment_exchange_rate": { "exchange_rate": "4243.80", "rate_date": "2025-05-05", "currency_id": 188 }, "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UNI", "line_extension_amount": "4243.80", "free_of_charge_indicator": false, "description": "HONORARIOS REPRESENTACION LEGAL ECUADOR", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4243.80", "base_quantity": 1.00 } ], "customer": { "country_id": "239", "identity_document_id": "10", "type_organization_id": 1, "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "legal_monetary_totals": { "line_extension_amount": "4243.80", "tax_exclusive_amount": "0", "tax_inclusive_amount": "4243.80", "payable_amount": 4243.80 } } ``` --- # Factura en USD Exportación A continuación se muestra un ejemplo de un JSON que representa una factura en dólares. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice_usd.json" { "resolution_number":"18764074347312", "prefix":"LZT", "document_number":"2055", "operation_type_id": 1, "type_document_id": 8, "currency_id": 272, "payments": [ { "payment_method_id": 1, "means_payment_id": 42, "value_paid": "4243.80" } ], "payment_exchange_rate": { "exchange_rate": "4243.80", "base_rate": "4243.80", "rate_date": "2025-05-05", "currency_id": 188 }, "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UNI", "line_extension_amount": "4243.80", "free_of_charge_indicator": false, "description": "HONORARIOS REPRESENTACION LEGAL ECUADOR", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4243.80", "base_quantity": 1.00 } ], "customer": { "country_id": "239", "identity_document_id": "10", "type_organization_id": 1, "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "legal_monetary_totals": { "line_extension_amount": "4243.80", "tax_exclusive_amount": "0", "tax_inclusive_amount": "4243.80", "payable_amount": 4243.80 } } ``` --- # Factura en Euros A continuación se muestra un ejemplo de un JSON que representa una factura en euros. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="invoice-euro.json" { "date": "2022-02-24", "time": "12:57:53", "document_number": "990000369", "operation_type_id": 1, "type_document_id": 7, "payments": [{ "payment_method_id": 2, "means_payment_id": 30, "value_paid": "1372.62" }], "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UNI", "line_extension_amount": "137.00", "free_of_charge_indicator": "false", "description": "HONORARIOS Notificación sanitaria ASEO Purificador de aire acondicionado", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "163.03", "base_quantity": 1.0000, "tax_totals": [ { "tax_id": "1", "tax_amount": 26.03, "taxable_amount": 137.00, "percent": 19 } ] }, { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UNI", "line_extension_amount": "137.00", "free_of_charge_indicator": "false", "description": "HONORARIOS Notificación sanitaria ASEO Aceite multiusos todo en 1", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "163.03", "base_quantity": 1.0000, "tax_totals": [ { "tax_id": "1", "tax_amount": 26.03, "taxable_amount": 137.00, "percent": 19 } ] }, { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "", "line_extension_amount": "112.00", "free_of_charge_indicator": "false", "description": "ORGANIZACION FICHA TECNICA Purificador de aire acondicionado", "code": "999-003", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "133.28", "base_quantity": 1.0000, "tax_totals": [ { "tax_id": "1", "tax_amount": 21.28, "taxable_amount": 112.00, "percent": 19 } ] }, { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "", "line_extension_amount": "112.00", "free_of_charge_indicator": "false", "description": "ORGANIZACION FICHA TECNICA Aceite multiusos todo en 1", "code": "999-003", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "133.28", "base_quantity": 1.0000, "tax_totals": [ { "tax_id": "1", "tax_amount": 21.28, "taxable_amount": 112.00, "percent": 19 } ] }, { "invoiced_quantity": "2.00", "quantity_units_id": "1093", "um": "", "line_extension_amount": "780.00", "free_of_charge_indicator": "false", "description": "PAGOS OFICIALES", "code": "999-002", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "390.00", "base_quantity": 2.0000, "tax_totals": [ { "tax_id": "1", "tax_amount": 0.0, "taxable_amount": 0.0, "percent": 0 } ] } ], "customer": { "country_id": "214", "city_id": "149", "identity_document_id": "10", "type_organization_id": 1, "tax_regime_id": 1, "tax_level_id": "5", "company_name": "QUIMI ROMAR S.L.", "dni": "444444016", "email": "cliente5@ejemplo.com", "address": "CRTA MONCADA - NAQUERA CV 315 KM 11.2" }, "legal_monetary_totals": { "line_extension_amount": "1278.00", "tax_exclusive_amount": "498.00", "tax_inclusive_amount": "1372.62", "payable_amount": "1372.62" }, "tax_totals": [ { "tax_id": 1, "tax_amount": "94.62", "taxable_amount": "498.00", "percent": 19.0 } ], "payment_exchange_rate": { "exchange_rate": "4200.00", "rate_date": "2022-02-24", "currency_id": 213 } } ``` --- # ✈️ Factura de Exportación (USD) Una **Factura de Exportación Avanzada** incluye información logística de alto valor requerida habitualmente en el comercio internacional. Este ejemplo ilustra cómo emitir una factura de exportación en dólares (USD) reportando **Términos de Entrega (Incoterms)**, información detallada del **Transportador y Entrega**, y referencias a **Remisiones y Órdenes de Pedido**, además de metadatos del artículo en las líneas (como lote y fecha de expiración). --- ## 💡 Reglas Técnicas y Logísticas :::tip ⚠️ Características Avanzadas del Payload * **Incoterms (`delivery_terms`):** Términos comerciales internacionales (ej. *Portes Pagados*, *FOB*, *CIF*). Permite definir responsabilidades de costo y riesgo. * **Datos de Entrega (`deliveries`):** Obligatorio en operaciones logísticas complejas. Registra la fecha de despacho, dirección en el extranjero, datos del transportista y su contacto. * **Trazabilidad (`document_references`):** Permite vincular remisiones de salida (`despatch`), recepciones (`receipt`) y la orden de compra (`order_reference`). * **Metadatos de Línea (`extra_data`):** Permite imprimir información específica de control sanitario o de inventario en el PDF (como número de **Lote** y **Fecha de Expiración**). * **Tasa de Cambio:** Se reporta la conversión a pesos colombianos (COP). *Nota: Se ha removido el campo obsoleto `base_rate` quedando únicamente la TRM en `exchange_rate`.* ::: --- ## 📋 Diccionario de Módulos Logísticos | Módulo JSON | Propósito Técnico | Elementos Clave | |-------------|-------------------|-----------------| | **`delivery_terms`** | Define los términos de entrega (Incoterms). | `terms` (glosa), `delivery_id` (código oficial). | | **`deliveries`** | Contenedor físico del despacho y transporte. | `date`, `address`, `country_id` (lugar de entrega). | | ↳ `delivery_party` | Datos fiscales de la empresa transportadora. | `company_name`, `dni`, `address`, `city_id`. | | ↳ `delivery_contact` | Persona de contacto del transporte para notificaciones.| `email`, `mobile`, `contact_name`. | | **`despatch_document_references`** | Vincula documentos de despacho previo (Remisión). | `number` (folio remisión), `date` (fecha). | | **`receipt_document_references`** | Vincula actas o documentos de recibo de mercancía. | `number`, `date`. | | **`order_reference`** | Asocia la orden de pedido / orden de compra internacional. | `reference_number`, `reference_date`. | | **`extra_data`** *(en línea)* | Datos de impresión dinámica en la línea del documento. | `title` (ej. LOTE), `value`, `align` (alineación PDF). | --- ## 📦 Estructura del JSON (Payload Completo) A continuación, se detalla el JSON avanzado estructurado para el envío a la API de Matias: **📦 Ver JSON de Exportación Avanzada (USD)** ```json title="invoice_usd_exportation_advanced.json" { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "5947", "operation_type_id": 1, "type_document_id": 8, "currency_id": 272, "payments": [ { "payment_method_id": 1, "means_payment_id": 42, "value_paid": "4243.80" } ], "payment_exchange_rate": { "exchange_rate": "4243.80", "rate_date": "2025-05-05", "currency_id": 188 }, "delivery_terms": { "terms": "Portes Pagados", "delivery_id": 1 }, "despatch_document_references": [ { "number": "8124167214 DA", "date": "2026-05-21" } ], "receipt_document_references": [ { "number": "12314129 GR", "date": "2026-05-21" } ], "order_reference": { "reference_number": "4541212", "reference_date": "2025-06-01" }, "deliveries": [ { "date": "2026-05-21", "time": "14:00:00", "address": "NORTE DE VIRGINIA H10", "country_id": "239", "delivery_party": { "identity_document_id": "10", "type_organization_id": 1, "tax_regime_id": 2, "tax_level_id": "5", "company_name": "Nombre de la empresa transportadora", "address": "cra prueba", "dni": "1019016005", "country_id": "45", "city_id": "149", "postal_code": "110121", "merchant_registration": "12454" }, "delivery_contact": { "email": "cliente1@ejemplo.com", "mobile": "3164444444", "contact_name": "Nombre del contacto", "note": "Nota adicional de contacto" } } ], "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UNI", "line_extension_amount": "4243.80", "free_of_charge_indicator": false, "description": "HONORARIOS REPRESENTACION LEGAL ECUADOR", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4243.80", "base_quantity": 1.00, "brand_name": "Nombre de la marca", "model_name": "Modelo del articulo", "extra_data": [ { "title": "LOTE", "value": "45413", "align": "left" }, { "title": "FECHA DE EXPIRACIÓN", "value": "02/02/2026", "align": "center" } ] } ], "customer": { "country_id": "239", "identity_document_id": "10", "type_organization_id": 1, "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "legal_monetary_totals": { "line_extension_amount": "4243.80", "tax_exclusive_amount": "0", "tax_inclusive_amount": "4243.80", "payable_amount": 4243.80 } } ``` --- ## 📊 Reglas de Validación DIAN - [ ] **Tipo de Factura:** El campo `type_document_id` debe ser `8`. - [ ] **TRM Reportada:** El campo `exchange_rate` dentro de `payment_exchange_rate` debe contener la tasa oficial. *(Nota: Recuerde no enviar `base_rate` ya que está en desuso)*. - [ ] **Incoterms Oficiales:** El código `delivery_id` en `delivery_terms` debe corresponder a la tabla paramétrica oficial DIAN. - [ ] **Líneas Libres de IVA:** Al ser una exportación, la línea no debe contener nodos de impuestos ni totales de IVA. --- # Factura venta de divisas A continuación se muestra un ejemplo de un JSON que representa una factura. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="currency-sale.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "864", "graphic_representation": 0, "send_email": 0, "operation_type_id": 7, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "13300.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "70", "line_extension_amount": "8500.00", "free_of_charge_indicator": false, "description": "DOLARES", "code": "18234000-DL", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4250", "base_quantity": "2", "um": "U" }, { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "4800.00", "free_of_charge_indicator": false, "description": "EUROS", "code": "18234000-EU", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4800", "base_quantity": "1" } ], "legal_monetary_totals": { "line_extension_amount": "13300.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "13300.00", "payable_amount": 13300.00 } } ``` --- # Factura compra de divisas A continuación se muestra un ejemplo de un JSON que representa una factura. Este JSON se puede utilizar para pruebas o para simular una factura real. ```json title="currency-purchase.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "861", "graphic_representation": 0, "send_email": 1, "operation_type_id": 6, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "12500.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "70", "line_extension_amount": "8000.00", "free_of_charge_indicator": false, "description": "DOLARES", "code": "18234000-DL", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4000", "base_quantity": "2", "um": "U" }, { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "4500.00", "free_of_charge_indicator": false, "description": "EUROS", "code": "18234000-EU", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4500", "base_quantity": "1" } ], "legal_monetary_totals": { "line_extension_amount": "12500.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "12500.00", "payable_amount": 12500.00 } } ``` --- # Nota Crédito A continuación se muestra un ejemplo de un JSON que representa una nota crédito. Este JSON se puede utilizar para pruebas o para simular una nota crédito real. ```json title="credit-note.json" { "resolution_number": "18763005880468", "prefix": "NC", "notes": "Documento anulado por errores en datos del cliente", "document_number": "214", "operation_type_id": 12, "type_document_id": 5, "graphic_representation": 0, "send_email": 0, "discrepancy_response": { "reference_id": "FVP1", "response_id": "2" }, "billing_reference": { "number": "FVP1", "date": "2024-05-24", "uuid": "43433a047058be691f874d0f114fbff8481c69d89cc79b2a308db048aa1f0d45b1c1d9dcf46e3e01dd3449cf5a386960" }, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "customer": { "country_id": "170", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Nota Crédito sin referencia a factura A continuación se muestra un ejemplo de un JSON que representa una nota crédito. Este JSON se puede utilizar para pruebas o para simular una nota crédito real. ```json title="credit-note-without-reference-to-invoice.json" { "resolution_number": "18763005880468", "prefix": "NC", "notes": "Documento anulado por errores en datos del cliente", "document_number": "325", "operation_type_id": 15, "type_document_id": 5, "graphic_representation": 1, "send_email": 1, "invoice_period": { "start_date": "2024-11-01", "start_time": "19:43:00", "end_date": "2024-11-30", "end_time": "19:43:00" }, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "customer": { "country_id": "45", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Lewis Lopez", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Nota Débito A continuación se muestra un ejemplo de un JSON que representa una nota débito. Este JSON se puede utilizar para pruebas o para simular una nota débito real. ```json title="debit-note.json" { "resolution_number": "18763005880468", "date": "2024-01-24", "time": "14:49:36", "document_number": "18", "operation_type_id": 1, "type_document_id": 4, "graphic_representation": 1, "send_email": 1, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "95000.00" }], "customer": { "country_id": "45", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "discrepancy_response": { "reference_id": "8003", "response_id": "9" }, "billing_reference": { "number": "8003", "date": "2023-12-22", "uuid": "8cbf2a265778d1360bebf75b704af307e875bfc3926ffc3b9135c98c469c34f6cf7a1b5df7093638e883a078f92d9d2e" }, "lines": [ { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 79831.93, "free_of_charge_indicator": false, "description": "STILOGRAFOLAMYALSTAR025M", "code": "4014519428527", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 95000, "base_quantity": 1, "tax_totals": [ { "tax_id": 1, "tax_amount": 15168.07, "taxable_amount": 79831.93, "percent": "19" } ] } ], "legal_monetary_totals": { "line_extension_amount": 79831.93, "tax_exclusive_amount": 79831.93, "tax_inclusive_amount": 95000, "charge_total_amount": 0, "pre_paid_amount": 0, "allowance_total_amount": 0, "payable_amount": 95000 }, "tax_totals": [ { "tax_id": 1, "tax_amount": 15168.07, "taxable_amount": 79831.93, "percent": "19" } ] } ``` --- # Factura de Contingencia Tipo 03 (Talonario o Papel) **⚠️ IMPORTANTE: Transmisión de Factura de Papel** Factura electrónica emitida como **transmisión digital** de una factura física (talonario/papel) previamente entregada al cliente durante un inconveniente tecnológico. Este tipo de factura **NO es una factura nueva**, sino el reporte digital de una que ya existe físicamente. **El nodo `additional_document_reference` es OBLIGATORIO y debe contener los datos de la factura de papel (número, fecha, código interno), NO un CUFE (porque el papel no lo tiene).** ## Información General - **Tipo de Documento:** 03 - **Descripción:** Factura de Contingencia (Talonario o Papel) - **Campo Requerido:** `additional_document_reference` ⚠️ **OBLIGATORIO PARA ESTE TIPO** - **Normativa:** Resolución No. 000165 (01/NOV/2023) - DIAN - Página 389 - **Casos de Uso:** - Facturación de contingencia por fallo del sistema - Regularización de facturas emitidas en papel previamente - Documentos de talonario que requieren respaldo electrónico ## Estructura JSON ```json { "resolution_number": "18764100103754", "prefix": "LCON", "notes": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "document_number": "2", "report_header": { "uuid": "101413670038274164", "vars": [ { "name": "sucursal", "value": "Bodega Principal Cali" }, { "name": "direccion", "value": "Zona Franca Palmaseca Bodega 5" }, { "name": "celular", "value": "315 112 4411" } ] }, "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 9, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" } ], "order_reference": { "reference_number": "4541212", "reference_date": "2025-06-01" }, "additional_document_reference": [ { "number": "LZT2119", "code": "TALONARIO", "date": "2025-08-18" } ], "document_signature": { "cashier": "Nombre del cajero(a)", "cashier_title": "Firma Cajero(a)", "seller": "Nombre del vendedor(a)", "seller_title": "Firma del vendedor(a)" }, "customer": { "identity_document_id": "3", "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente3@ejemplo.com" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.0 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` ## Campos Destacados ### `type_document_id: 9` Identificador para factura de contingencia tipo 03 (Talonario o Papel). ### `additional_document_reference` ⚠️ OBLIGATORIO **🚨 CRÍTICO: NO use CUFE en este nodo** En este grupo **NO debe colocar un CUFE** (porque el papel no lo tiene), sino los **datos de la factura de papel** que tiene en la mano. Campo que contiene los datos de la factura física (talonario/papel) previamente entregada al cliente: ```json { "number": "LZT2119", // Prefijo y número de la factura de papel "code": "TALONARIO", // Código interno de la empresa (NO estandarizado DIAN) "date": "2025-08-18" // Fecha en que se generó y entregó el papel } ``` **Campos:** - **`number`** 🔴 OBLIGATORIO: Prefijo y número de la factura de papel que entregó al cliente - **`date`** 🔴 OBLIGATORIO: Fecha en que generó y entregó la factura de papel (NO la fecha de transmisión) - **`code`** 🟢 OPCIONAL: Código interno de la empresa. Ejemplos: "TALONARIO", "PAPEL", "FC", "01" ### `order_reference` Referencia opcional a documento adicional u orden de compra. ### `document_signature` Firmas de autorización (cajero y vendedor) para documentos de contingencia. ## Validaciones DIAN - ✅ `additional_document_reference` es **obligatorio** para Tipo 03 - ✅ El `number` debe corresponder al número de la factura de papel - ✅ La `date` es **obligatoria** y debe ser la fecha del papel (regla FAI05) - ✅ El `code` es opcional (codificación propia de la empresa) - ❌ **NO se debe incluir `uuid`** (el papel no tiene CUFE) - ❌ **NO se debe incluir `scheme_name`** (no aplica para papel) ## Casos de Rechazo | Condición | Error | | --------------------------------------- | --------------------------------------------- | | Falta `additional_document_reference` | Rechazo FAI01: Campo obligatorio para tipo 03 | | Falta `number` | Rechazo FAI02: ID de documento no informado | | Falta `date` | Rechazo FAI05: Fecha no informada | | Se incluye `uuid` para factura de papel | Error: El papel no tiene CUFE | ## Proceso Correcto de Transmisión 1. **Expedir factura física:** Durante el inconveniente tecnológico, se expide una factura física (talonario o papel) y se entrega al cliente. 2. **Superar el inconveniente:** Una vez restablecido el servicio tecnológico. 3. **Transmitir el XML:** Dentro de las **48 horas siguientes** a la superación del inconveniente, debe transmitir el XML con: - `type_document_id = 9` (Tipo 03 en API) - Nodo `additional_document_reference` con los datos del papel (número, fecha, código) 4. **Firmar y enviar:** Firme digitalmente el XML y envíelo a la DIAN. ## Notas Crédito/Débito para Facturas Tipo 03 **⚠️ IMPORTANTE: NO existe "Contingencia" para Notas** Las notas crédito y débito **NO tienen esquema de contingencia (Tipo 03)**. **Proceso correcto:** 1. Transmitir la factura Tipo 03 a la DIAN 2. Obtener el CUFE asignado por la DIAN 3. Generar la Nota electrónica estándar referenciando ese CUFE en `billing_reference` ## Relación con Otros Documentos - **Documento Original:** Factura emitida en papel o talonario durante inconveniente tecnológico - **Documento Electrónico:** Esta factura tipo 03 (transmisión digital del papel) - **Propósito:** Reportar digitalmente a la DIAN una venta que ya ocurrió mediante papel - **Soporte Legal:** El papel físico hasta que se transmita; luego el XML validado ## Ejemplos de Uso ### Caso 1: Contingencia por Sistema Caído Factura emitida en papel durante fallo del sistema. Se ingresa posteriormente como tipo 03 para regularización. ### Caso 2: Talonario Anterior Factura de talonario emitida previo a la implementación de facturación electrónica. Se crea tipo 03 referenciando el documento original. ### Caso 3: Migración de Sistema Antiguo Documentos legados en papel que requieren respaldo electrónico mediante factura tipo 03. ## Campos Opcionales - `notes`: Notas generales del documento - `order_reference`: Referencia a orden de compra - `graphic_representation`: Representación gráfica - `send_email`: Envío automático por correo ## Referencias - Resolución DIAN No. 000165 (01/NOV/2023) - Anexo Técnico de Facturación Electrónica v2.1 - Catálogos DIAN (type_document_id, identity_document_id, etc.) --- # Factura de Contingencia Tipo 04 (Manual) Factura electrónica emitida como respaldo de un documento generado manualmente o mediante proceso manual. Similar a la tipo 03, pero sin la obligatoriedad de `additional_document_reference`. ## Información General - **Tipo de Documento:** 04 - **Descripción:** Factura de Contingencia (Manual) - **Campo Requerido:** `additional_document_reference` ⚠️ **OPCIONAL para este tipo** (Obligatorio SOLO para tipo 03) - **Normativa:** Resolución No. 000165 (01/NOV/2023) - DIAN - Página 389 - **Casos de Uso:** - Facturación manual de documentos no sistematizados - Respaldo de facturas generadas mediante procesos alternativos - Documentos creados manualmente que requieren respaldo electrónico ## Estructura JSON ```json { "resolution_number": "18764100103754", "prefix": "LCON", "notes": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "document_number": "3", "report_header": { "uuid": "101413670038274164", "vars": [ { "name": "sucursal", "value": "Bodega Principal Cali" }, { "name": "direccion", "value": "Zona Franca Palmaseca Bodega 5" }, { "name": "celular", "value": "315 112 4411" } ] }, "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 10, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "order_reference": { "reference_number": "4541212", "reference_date": "2025-06-01" }, "document_signature": { "cashier": "Nombre del cajero(a)", "cashier_title": "Firma Cajero(a)", "seller": "Nombre del vendedor(a)", "seller_title": "Firma del vendedor(a)" }, "customer": { "identity_document_id": "3", "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente3@ejemplo.com" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` ## Campos Destacados ### `type_document_id: 10` Identificador para factura de contingencia tipo 04 (Manual). ### `document_signature` ⚠️ IMPORTANTE Para facturas manuales, es especialmente importante incluir las firmas de autorización: ```json { "cashier": "Nombre del cajero(a)", "cashier_title": "Firma Cajero(a)", "seller": "Nombre del vendedor(a)", "seller_title": "Firma del vendedor(a)" } ``` ### `order_reference` Referencia a documento generador (orden de compra, solicitud, etc.). ### `additional_document_reference` (Opcional) A diferencia de tipo 03, este campo es **opcional** para tipo 04. Si se incluye, debe seguir la misma estructura. ## Validaciones DIAN - ✅ `type_document_id` debe ser **10** - ✅ `document_signature` es recomendado - ✅ Debe informarse claramente el origen manual del documento - ✅ `order_reference` ayuda a trazabilidad ## Diferencias con Tipo 03 | Aspecto | Tipo 03 | Tipo 04 | |--------|--------|--------| | Descripción | Talonario/Papel | Manual | | `additional_document_reference` | Obligatorio | Opcional | | `document_signature` | Recomendado | Muy Recomendado | | Caso de Uso | Papel/Talonario previo | Procesos manuales | | `type_document_id` | 9 | 10 | ## Casos de Rechazo | Condición | Error | |-----------|-------| | `type_document_id` incorrecto | Rechazo: Tipo de documento inválido | | Falta información crítica | Rechazo: Campo obligatorio | | Datos inconsistentes | Rechazo: Validación de datos | ## Casos de Uso ### Caso 1: Facturación Manual en Punto de Venta Documentos generados manualmente en punto de venta que requieren regularización electrónica. ### Caso 2: Proceso Alterno Facturas creadas mediante proceso alterno o excepcional que requieren respaldo electrónico. ### Caso 3: Documentos Sin Sistematizar Documentos históricos que no fueron sistematizados y requieren respaldo electrónico. ## Campos Recomendados - `notes`: Explicar contexto manual/excepcional - `order_reference`: Vincular a documento generador - `document_signature`: Firmas de autorización - `report_header`: Información de sucursal/ubicación ## Campos Opcionales - `graphic_representation`: Representación gráfica - `send_email`: Envío automático por correo - `additional_document_reference`: Referencia a documento generador ## Referencias - Resolución DIAN No. 000165 (01/NOV/2023) - Anexo Técnico de Facturación Electrónica v2.1 - Catálogos DIAN (type_document_id, identity_document_id, etc.) ## Trazabilidad y Auditoría Para facturas tipo 04, se recomienda: 1. Mantener registro de autorización (firmas) 2. Documentar razón de generación manual 3. Incluir referencias cruzadas con documentos generadores 4. Guardar evidencia de excepcionalidad del proceso --- # Nota Crédito para Factura de Contingencia Tipo 03 **⚠️ IMPORTANTE: Proceso Especial para Notas de Facturas Tipo 03** Las notas crédito/débito **NO tienen esquema de contingencia**. Para generar una nota que afecte a una factura de contingencia Tipo 03, primero debe transmitir la factura Tipo 03 a la DIAN, obtener el CUFE asignado, y luego generar la nota electrónica estándar referenciando ese CUFE. ## Información General - **Tipo de Documento:** 5 (Nota Crédito) - **Factura Referenciada:** Tipo 03 (Contingencia) - **Campo Crítico:** `billing_reference` con el **CUFE asignado por la DIAN** a la factura Tipo 03 - **Normativa:** Anexo Técnico de Factura Electrónica v1.9 ## Proceso Correcto 1. **Transmitir Factura Tipo 03:** Primero debe transmitir la factura de papel (XML Tipo 03) a la DIAN 2. **Obtener CUFE:** La DIAN valida el documento y le asigna un CUFE 3. **Generar Nota Electrónica:** Solo entonces puede generar esta Nota Crédito electrónica estándar 4. **Referenciar CUFE:** Use el CUFE asignado en el nodo `billing_reference` ## Estructura JSON ```json title="credit-note-type-03.json" { "resolution_number": "18763005880468", "prefix": "NC", "notes": "Documento anulado, ejemplo documento contingencia tipo 03", "document_number": "762", "operation_type_id": 12, "type_document_id": 5, "graphic_representation": 1, "send_email": 1, "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "discrepancy_response": { "reference_id": "LCON4", "response_id": "2" }, "billing_reference": { "number": "LCON4", "date": "2026-02-14", "uuid": "0f7d72aba9fb69ad59f817d6ae0343452dff6477436129a739f4391610634c39927fbf42b0447e83fd971f84f2dab7da" }, "order_reference": { "reference_number": "4541212", "reference_date": "2025-06-01" }, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" } ], "customer": { "identity_document_id": "3", "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente3@ejemplo.com" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.0 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` ## Campos Destacados ### `type_document_id: 5` Identificador para Nota Crédito electrónica estándar. **NO existe Nota Crédito tipo 03 (contingencia)**. ### `billing_reference` 🔴 OBLIGATORIO **💡 Punto Clave:** Debe referenciar el CUFE que la DIAN asignó a la factura Tipo 03 transmitida. Campo que referencia la factura de contingencia Tipo 03 ya transmitida y validada: ```json { "number": "LCON4", // Prefijo y número de la factura Tipo 03 "date": "2026-02-14", // Fecha de la factura Tipo 03 "uuid": "0f7d72ab..." // CUFE asignado por DIAN a la factura Tipo 03 } ``` **Campos:** - **`number`** 🔴 OBLIGATORIO: Prefijo y número de la factura Tipo 03 - **`date`** 🔴 OBLIGATORIO: Fecha de emisión de la factura Tipo 03 - **`uuid`** 🔴 OBLIGATORIO: CUFE que la DIAN asignó a la factura Tipo 03 (NO el número del papel) ### `discrepancy_response` 🔴 OBLIGATORIO Respuesta a la discrepancia que motiva la nota: ```json { "reference_id": "LCON4", // Número de la factura referenciada "response_id": "2" // Código del motivo de corrección } ``` **Códigos comunes de `response_id`:** - `1` - Devolución parcial de bienes - `2` - Anulación de factura electrónica - `3` - Rebaja total aplicada - `4` - Descuento total aplicado - `5` - Rescisión: nulidad por falta de requisitos ### `operation_type_id: 12` Tipo de operación para notas crédito. ## Diferencias con Nota Crédito Normal | Característica | Nota Crédito Normal | Nota Crédito para Tipo 03 | | :---------------------------- | :--------------------------- | :--------------------------------------- | | **Tipo de Nota** | Electrónica Estándar (5) | Electrónica Estándar (5) | | **Prerrequisito** | Factura validada | Factura Tipo 03 transmitida y validada | | **UUID en billing_reference** | CUFE de factura normal | CUFE asignado por DIAN a factura Tipo 03 | | **Timing** | Inmediato después de factura | Después de transmitir y validar Tipo 03 | ## Validaciones DIAN - ✅ `billing_reference` es **obligatorio** - ✅ El `uuid` debe corresponder al CUFE asignado por DIAN a la factura Tipo 03 - ✅ El `number` debe coincidir con el número de la factura Tipo 03 - ✅ La `date` debe coincidir con la fecha de la factura Tipo 03 - ✅ `discrepancy_response` es **obligatorio** - ❌ **NO se puede generar** antes de transmitir la factura Tipo 03 ## Casos de Rechazo | Condición | Error | | --------------------------------- | --------------------------------------- | | Falta `billing_reference` | Rechazo: Campo obligatorio para notas | | UUID no corresponde a CUFE válido | Rechazo: CUFE inválido o no encontrado | | Factura Tipo 03 no transmitida | Rechazo: Factura referenciada no existe | | Falta `discrepancy_response` | Rechazo: Campo obligatorio | | Número no coincide con factura | Rechazo: Referencia incorrecta | ## Ejemplo de Flujo Completo ### Paso 1: Transmitir Factura Tipo 03 ```json { "type_document_id": 9, "prefix": "LCON", "document_number": "4", "additional_document_reference": [ { "number": "PAPEL-004", "date": "2026-02-14", "code": "TALONARIO" } ] // ... otros campos } ``` ### Paso 2: DIAN Asigna CUFE Respuesta de la DIAN: ```json { "uuid": "0f7d72aba9fb69ad59f817d6ae0343452dff6477436129a739f4391610634c39927fbf42b0447e83fd971f84f2dab7da" } ``` ### Paso 3: Generar Nota Crédito Usar el CUFE asignado en `billing_reference.uuid` (como se muestra en el ejemplo principal arriba). ## Referencias - Anexo Técnico de Factura Electrónica v1.9 - Resolución DIAN No. 000165 (01/NOV/2023) - [Factura de Contingencia Tipo 03](https://docs.matias-api.com/docs/jsons-billing/contingency-invoice-type-03.md) - [Campos de Facturación - billing_reference](https://docs.matias-api.com/docs/billing-fields#billing_reference-) --- # Factura Mandatos Factura que incluye información de mandatarios en los ítems. Los mandatos aplican cuando se venden productos o servicios en nombre de terceros. ## JSON de Ejemplo ```json { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "document_number": "2123", "report_header": { "uuid": "101413670038274164", "vars": [ { "name": "sucursal", "value": "Bodega Principal Cali" }, { "name": "direccion", "value": "Zona Franca Palmaseca Bodega 5" }, { "name": "celular", "value": "315 112 4411" } ] }, "graphic_representation": 0, "send_email": 1, "operation_type_id": 3, "type_document_id": 7, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "order_reference": { "reference_number": "4541212", "reference_date": "2025-06-01" }, "document_signature": { "cashier": "Nombre del cajero(a)", "cashier_title": "Firma Cajero(a)", "seller": "Nombre del vendedor(a)", "seller_title": "Firma del vendedor(a)" }, "customer": { "identity_document_id": "3", "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente3@ejemplo.com" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "um": "M", "mandate": { "dni": "2222222222", "dv": "2", "code": "1" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "mandate": { "dni": "2222222222", "dv": "2", "code": "0" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` ## Notas Importantes - **Mandatos**: Los mandatos se informan a nivel de ítem en el campo `mandate` - **Obligatorio**: Este campo es obligatorio solo para facturas de mandato - **Códigos de Mandato**: - `0`: B/S ingreso propio - `1`: B/S Ingresos Recibidos para Terceros - **Información del Mandatario**: Incluye `dni`, `dv` (dígito de verificación) y `code` (tipo de ingreso) Para más detalles, consulte la sección [Campos de todos los documentos electrónicos](https://docs.matias-api.com/docs/billing-fields#lines-mandate) en la documentación. --- # Factura con Impuesto Nominal A continuación se muestra un ejemplo de un JSON que representa una factura con impuesto nominal (valor fijo por unidad). Este tipo de impuesto **no** se calcula como un porcentaje sobre una base gravable, sino como un valor monetario fijo multiplicado por la cantidad de unidades. Los campos `taxable_amount` y `percent` deben enviarse con valor `0`. :::warning Regla de validación DIAN La DIAN valida matemáticamente que `tax_amount = per_unit_amount × base_unit_measure`. Una diferencia en el resultado genera rechazo por las reglas `FAX07` (línea) / `FAS07` (cabecera del documento). ::: ```json title="invoice-with-nominal-tax.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "3001", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "2233.01" } ], "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "1550.70", "free_of_charge_indicator": false, "description": "PRODUCTO SUJETO A IMPUESTO NOMINAL", "code": "PROD001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "1550.70", "base_quantity": "1", "tax_totals": [ { "tax_id": "1", "tax_amount": 294.633000, "taxable_amount": 1550.700000, "percent": 19 }, { "tax_id": "18", "tax_amount": 387.675000, "per_unit_amount": 387.675000, "base_unit_measure": 1, "quantity_units_id": 70, "taxable_amount": 0, "percent": 0 } ] } ], "legal_monetary_totals": { "line_extension_amount": "1550.70", "tax_exclusive_amount": "1550.70", "tax_inclusive_amount": "2233.008", "payable_amount": 2233.01 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 294.633000, "taxable_amount": 1550.700000, "percent": 19 }, { "tax_id": "18", "tax_amount": 387.675000, "per_unit_amount": 387.675000, "base_unit_measure": 1, "quantity_units_id": 70, "taxable_amount": 0, "percent": 0 } ] } ``` --- # Factura Impuesto Licores - AD VALOREM A continuación se muestra un ejemplo de un JSON que representa una factura con Impuesto al Consumo de Licores bajo la modalidad **Ad Valorem** (porcentual). A diferencia del impuesto nominal, el Ad Valorem se calcula como un **porcentaje sobre la base gravable** (`taxable_amount × percent / 100`), siguiendo el mismo patrón que el IVA. ```json title="invoice-licores-ad-valorem.json" { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "document_number": "4065", "report_header": { "uuid": "101413670038274164", "vars": [ { "name": "sucursal", "value": "Bodega Principal Cali" }, { "name": "direccion", "value": "Zona Franca Palmaseca Bodega 5" }, { "name": "celular", "value": "315 112 4411" } ] }, "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "2233.008" } ], "order_reference": { "reference_number": "4541212", "reference_date": "2025-06-01" }, "document_signature": { "cashier": "CAROLINA RODRIGUEZ", "seller": "BRIYI SIRLEY ARIAS MONTOYA" }, "customer": { "identity_document_id": "3", "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente3@ejemplo.com" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "70", "line_extension_amount": "1550.700000", "free_of_charge_indicator": false, "description": "POKER 330 X30", "code": "00725", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "1550.700000", "base_quantity": "1", "um": "UND", "tax_totals": [ { "tax_id": "1", "tax_amount": 294.633000, "taxable_amount": 1550.700000, "percent": 19 }, { "tax_id": "22", "tax_amount": 387.675000, "taxable_amount": 1550.700000, "percent": 25 } ] } ], "legal_monetary_totals": { "line_extension_amount": "1550.700000", "tax_exclusive_amount": "1550.700000", "tax_inclusive_amount": "2233.008000", "total_charges": "0.000000", "total_allowance": "0", "payable_amount": "2233.008000" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 294.633000, "taxable_amount": 1550.700000, "percent": 19.00 }, { "tax_id": "22", "tax_amount": 387.675000, "taxable_amount": 1550.700000, "percent": 25.00 } ] } ``` --- # 🏥 Factura Electrónica del Sector Salud La **Factura Electrónica de Venta (FEV) del Sector Salud** incorpora campos de datos específicos definidos por el Ministerio de Salud y Protección Social y la DIAN, actuando como soporte y validación previa del **Registro Individual de Prestación de Servicios de Salud (RIPS)**. :::info 📋 Marco Normativo Vigente Este documento cumple con las especificaciones de la **Resolución 000948 de 2026** (14 de mayo de 2026) —que deroga expresamente las Resoluciones 2275 de 2023, 558 y 1884 de 2024— y el **Documento Técnico 2 Versión 001** (*Campos de datos del sector salud adicionales a la generación de la FEV*). ::: --- ## 📦 Estructura del Payload JSON A continuación se presenta el payload completo para emitir una Factura Electrónica del Sector Salud a través de la API: ```json title="invoice_sector_salud.json" { "resolution_number": "18764112492320", "prefix": "LZT", "notes": "Nota del documento", "document_number": "6006", "graphic_representation": 1, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" } ], "health": { "operation_type": "SS-CUFE", "invoice_period": { "start_date": "2026-07-01", "start_time": "23:59:59", "end_date": "2026-07-30", "end_time": "23:59:59" }, "download_attachments": { "url": "www.ips-1.com.co", "arguments": [ { "name": "excelFile", "value": "a1b2c3.xlsx" }, { "name": "txtFile", "value": "a1b2c3.txt" } ] }, "document_delivery": { "ws": "https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl", "arguments": [ { "name": "Método-1", "value": "ClienteEntregaAcuseDeReciboDeFEV-VP" }, { "name": "Método-2", "value": "ClienteEntregaConstanciaDeMercanciaEntregada" } ] }, "provider_code": "2341702036", "payment_modality": "02", "coverage": "01", "contract_number": "c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c", "policy_number": "", "copayment": 0, "moderator_fee": 0, "shared_payments": 0, "advance": 0, "justification_without_contract": "" }, "customer": { "country_id": "170", "city_id": "149", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2" }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2" } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "200.00", "payable_amount": 200.00, "pre_paid_amount": "0" } } ``` --- ## 🔍 Desglose del Objeto `health` | Campo | Valor en Ejemplo | Descripción y Validación | |---|:---:|---| | `operation_type` | `"SS-CUFE"` | Tipo de operación en salud para factura electrónica con CUFE. | | `provider_code` | `"2341702036"` | Código de habilitación REPS del prestador asignado en el Registro Especial de Prestadores de Servicios de Salud. | | `payment_modality` | `"02"` | Modalidad de pago pactada (`02` = Pago global prospectivo). Catálogo `modalidadPago` de SISPRO. | | `coverage` | `"01"` | Cobertura o plan de beneficios (`01` = Plan de beneficios en salud financiado con UPC Contributiva). Catálogo `coberturaPlan`. | | `contract_number` | `"c12bab..."` | Número de contrato suscrito o código CUCON (Ley 1966 de 2019). | | `policy_number` | `""` | Número de póliza de salud o SOAT (aplica para coberturas `04` SOAT o `12` Pólizas voluntarias). | | `copayment` | `0` | Valor total de copagos recaudados al usuario (debe coincidir con RIPS). | | `moderator_fee` | `0` | Valor de cuotas moderadoras efectivamente recaudadas (debe coincidir con RIPS). | | `shared_payments` | `0` | Pagos compartidos en planes voluntarios de salud recaudados por el prestador. | | `advance` | `0` | Anticipos pactados a legalizar descontados del valor de la factura. | | `justification_without_contract` | `""` | Causal de atención sin contrato (Catálogo `facturaSinContrato` de SISPRO). Vacío cuando existe contrato. | | `invoice_period` | `{...}` | Fechas y horas de inicio (`start_date`, `start_time`) y fin (`end_date`, `end_time`) de la prestación del servicio. | | `download_attachments` | `{...}` | URL y lista de parámetros/archivos de soportes clínicos y RIPS adjuntos. | | `document_delivery` | `{...}` | Web service SOAP/WSDL o REST para entrega y recepción de acuses de la factura. | --- ## 💻 Ejemplos de Envío a la API **cURL** ```bash curl -X POST "{{url}}/invoice" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d @invoice_sector_salud.json ``` **JavaScript (Axios)** ```javascript import axios from 'axios'; const payload = { resolution_number: "18764112492320", prefix: "LZT", notes: "Nota del documento", document_number: "6006", graphic_representation: 1, send_email: 1, operation_type_id: 1, type_document_id: 7, payments: [ { payment_method_id: 1, means_payment_id: 10, value_paid: "224.00" } ], health: { operation_type: "SS-CUFE", invoice_period: { start_date: "2026-07-01", start_time: "23:59:59", end_date: "2026-07-30", end_time: "23:59:59" }, download_attachments: { url: "www.ips-1.com.co", arguments: [ { name: "excelFile", value: "a1b2c3.xlsx" }, { name: "txtFile", value: "a1b2c3.txt" } ] }, document_delivery: { ws: "https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl", arguments: [ { name: "Método-1", value: "ClienteEntregaAcuseDeReciboDeFEV-VP" }, { name: "Método-2", value: "ClienteEntregaConstanciaDeMercanciaEntregada" } ] }, provider_code: "2341702036", payment_modality: "02", coverage: "01", contract_number: "c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c", policy_number: "", copayment: 0, moderator_fee: 0, shared_payments: 0, advance: 0, justification_without_contract: "" }, customer: { country_id: "170", city_id: "149", identity_document_id: "1", type_organization_id: 2, tax_regime_id: 2, tax_level_id: 5, company_name: "Santiago Arango", dni: "1152440359", mobile: "3108435423", email: "cliente4@ejemplo.com", address: "Direccion residencial", postal_code: "661002" }, lines: [ { invoiced_quantity: "2", quantity_units_id: "1093", line_extension_amount: "100.00", free_of_charge_indicator: false, description: "TIJERA NECROPSIA AVES", code: "HMT83", type_item_identifications_id: "4", reference_price_id: "1", price_amount: "50", base_quantity: "2" }, { invoiced_quantity: "2", quantity_units_id: "1093", line_extension_amount: "100.00", free_of_charge_indicator: false, description: "TIJERA NECROPSIA AVES 2", code: "HMT84", type_item_identifications_id: "4", reference_price_id: "1", price_amount: "50", base_quantity: "2" } ], legal_monetary_totals: { line_extension_amount: "200.00", tax_exclusive_amount: "0.00", tax_inclusive_amount: "200.00", payable_amount: 200.00, pre_paid_amount: "0" } }; const response = await axios.post(`${url}/invoice`, payload, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); console.log(response.data); ``` **PHP (Guzzle)** ```php use GuzzleHttp\Client; $client = new Client(); $response = $client->post("{$url}/invoice", [ 'headers' => [ 'Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json', ], 'json' => [ 'resolution_number' => '18764112492320', 'prefix' => 'LZT', 'notes' => 'Nota del documento', 'document_number' => '6006', 'graphic_representation' => 1, 'send_email' => 1, 'operation_type_id' => 1, 'type_document_id' => 7, 'payments' => [ [ 'payment_method_id' => 1, 'means_payment_id' => 10, 'value_paid' => '224.00' ] ], 'health' => [ 'operation_type' => 'SS-CUFE', 'provider_code' => '2341702036', 'payment_modality' => '02', 'coverage' => '01', 'contract_number' => 'c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c', 'policy_number' => '', 'copayment' => 0, 'moderator_fee' => 0, 'shared_payments' => 0, 'advance' => 0, 'justification_without_contract' => '', 'invoice_period' => [ 'start_date' => '2026-07-01', 'start_time' => '23:59:59', 'end_date' => '2026-07-30', 'end_time' => '23:59:59' ], 'download_attachments' => [ 'url' => 'www.ips-1.com.co', 'arguments' => [ ['name' => 'excelFile', 'value' => 'a1b2c3.xlsx'], ['name' => 'txtFile', 'value' => 'a1b2c3.txt'] ] ], 'document_delivery' => [ 'ws' => 'https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl', 'arguments' => [ ['name' => 'Método-1', 'value' => 'ClienteEntregaAcuseDeReciboDeFEV-VP'], ['name' => 'Método-2', 'value' => 'ClienteEntregaConstanciaDeMercanciaEntregada'] ] ] ], 'customer' => [ 'country_id' => '170', 'city_id' => '149', 'identity_document_id' => '1', 'type_organization_id' => 2, 'tax_regime_id' => 2, 'tax_level_id' => 5, 'company_name' => 'Santiago Arango', 'dni' => '1152440359', 'mobile' => '3108435423', 'email' => 'cliente4@ejemplo.com', 'address' => 'Direccion residencial', 'postal_code' => '661002' ], 'lines' => [ [ 'invoiced_quantity' => '2', 'quantity_units_id' => '1093', 'line_extension_amount' => '100.00', 'free_of_charge_indicator' => false, 'description' => 'TIJERA NECROPSIA AVES', 'code' => 'HMT83', 'type_item_identifications_id' => '4', 'reference_price_id' => '1', 'price_amount' => '50', 'base_quantity' => '2' ], [ 'invoiced_quantity' => '2', 'quantity_units_id' => '1093', 'line_extension_amount' => '100.00', 'free_of_charge_indicator' => false, 'description' => 'TIJERA NECROPSIA AVES 2', 'code' => 'HMT84', 'type_item_identifications_id' => '4', 'reference_price_id' => '1', 'price_amount' => '50', 'base_quantity' => '2' ] ], 'legal_monetary_totals' => [ 'line_extension_amount' => '200.00', 'tax_exclusive_amount' => '0.00', 'tax_inclusive_amount' => '200.00', 'payable_amount' => 200.00, 'pre_paid_amount' => '0' ] ] ]); echo $response->getBody(); ``` --- ## 📋 Lista de Chequeo para Emisión en Salud - [ ] **Código REPS Válido:** `provider_code` debe corresponder al código de habilitación activo de la IPS o proveedor en el Registro Especial de Prestadores de Servicios de Salud. - [ ] **Modalidad y Cobertura:** `payment_modality` y `coverage` deben ser códigos válidos de las tablas SISPRO. - [ ] **Periodo de Facturación:** Las fechas de `invoice_period` deben cubrir los servicios prestados o el periodo contractual acordado. - [ ] **Sincronización con RIPS:** Los valores de `copayment`, `moderator_fee` y `shared_payments` deben coincidir exactamente con los montos consignados en la estructura del archivo RIPS JSON. --- # 🏥 Nota Crédito del Sector Salud La **Nota Crédito Electrónica del Sector Salud** se utiliza para realizar anulaciones, devoluciones, glosas, ajustes o correcciones a facturas electrónicas de servicios y tecnologías en salud previamente emitidas, manteniendo la trazabilidad e interoperabilidad con el **RIPS**. :::info 📋 Marco Normativo Vigente Este documento cumple con las especificaciones de la **Resolución 000948 de 2026** (14 de mayo de 2026) del Ministerio de Salud y Protección Social —que deroga expresamente las Resoluciones 2275 de 2023, 558 y 1884 de 2024— y el **Documento Técnico 2 Versión 001** (*Campos de datos del sector salud adicionales a la generación de la FEV*). ::: --- ## 📦 Estructura del Payload JSON A continuación se detalla el payload completo para emitir una Nota Crédito del Sector Salud referenciando una Factura Electrónica previa: ```json title="credit_note_sector_salud.json" { "resolution_number": "18763005880468", "prefix": "NC", "notes": "Documento anulado por errores en datos del cliente", "document_number": "7061", "operation_type_id": 12, "type_document_id": 5, "graphic_representation": 1, "send_email": 1, "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "discrepancy_response": { "reference_id": "LZT6004", "response_id": "2" }, "billing_reference": { "number": "LZT6004", "date": "2026-08-16", "uuid": "ba87dd080aefd0558251633ceae95bb6cfe99b18625370c098edc73765bd65914a0447288ab72d1e24913e36813a4289" }, "order_reference": { "reference_number": "4502878541", "reference_date": "2026-05-02" }, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" } ], "health": { "operation_type": "SS-CUDE", "invoice_period": { "start_date": "2026-07-01", "start_time": "23:59:59", "end_date": "2026-07-30", "end_time": "23:59:59" }, "download_attachments": { "url": "www.ips-1.com.co", "arguments": [ { "name": "excelFile", "value": "a1b2c3.xlsx" }, { "name": "txtFile", "value": "a1b2c3.txt" } ] }, "document_delivery": { "ws": "https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl", "arguments": [ { "name": "Método-1", "value": "ClienteEntregaAcuseDeReciboDeFEV-VP" }, { "name": "Método-2", "value": "ClienteEntregaConstanciaDeMercanciaEntregada" } ] }, "provider_code": "2341702036", "payment_modality": "02", "coverage": "01", "contract_number": "c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c", "policy_number": "", "copayment": 0, "moderator_fee": 0, "shared_payments": 0, "advance": 0, "justification_without_contract": "" }, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- ## 🔍 Parámetros Clave de la Nota Crédito en Salud | Campo | Valor en Ejemplo | Descripción | |---|:---:|---| | `operation_type_id` | `12` | Tipo de operación: Nota Crédito que referencia una Factura Electrónica. | | `type_document_id` | `5` | Tipo de documento DIAN: **Nota Crédito**. | | `health.operation_type` | `"SS-CUDE"` | Identificador de operación del sector salud para documentos con CUDE (Notas Crédito / Débito). | | `discrepancy_response` | `{ "reference_id": "LZT6004", "response_id": "2" }` | Concepto de corrección DIAN (`2` = Anulación de factura electrónica). | | `billing_reference` | `{ "number": "LZT6004", "uuid": "..." }` | Referencia a la factura electrónica original afectada (número, fecha y CUFE). | | `health.provider_code` | `"2341702036"` | Código de habilitación REPS del prestador emisor. | | `health.payment_modality` | `"02"` | Modalidad de pago pactada (`02` = Pago global prospectivo). | | `health.coverage` | `"01"` | Cobertura o plan de beneficios (`01` = UPC Contributiva). | --- ## 💻 Ejemplos de Envío a la API **cURL** ```bash curl -X POST "{{url}}/notes/credit" \ -H "Authorization: Bearer {token}" \ -H "Content-Type: application/json" \ -d @credit_note_sector_salud.json ``` **JavaScript (Axios)** ```javascript import axios from 'axios'; const payload = { resolution_number: "18763005880468", prefix: "NC", notes: "Documento anulado por errores en datos del cliente", document_number: "7061", operation_type_id: 12, type_document_id: 5, graphic_representation: 1, send_email: 1, document_signature: { cashier: "Nombre del cajero(a)", seller: "Nombre del vendedor(a)" }, discrepancy_response: { reference_id: "LZT6004", response_id: "2" }, billing_reference: { number: "LZT6004", date: "2026-08-16", uuid: "ba87dd080aefd0558251633ceae95bb6cfe99b18625370c098edc73765bd65914a0447288ab72d1e24913e36813a4289" }, order_reference: { reference_number: "4502878541", reference_date: "2026-05-02" }, payments: [ { payment_method_id: 1, means_payment_id: 10, value_paid: "224.00" } ], health: { operation_type: "SS-CUDE", invoice_period: { start_date: "2026-07-01", start_time: "23:59:59", end_date: "2026-07-30", end_time: "23:59:59" }, download_attachments: { url: "www.ips-1.com.co", arguments: [ { name: "excelFile", value: "a1b2c3.xlsx" }, { name: "txtFile", value: "a1b2c3.txt" } ] }, document_delivery: { ws: "https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl", arguments: [ { name: "Método-1", value: "ClienteEntregaAcuseDeReciboDeFEV-VP" }, { name: "Método-2", value: "ClienteEntregaConstanciaDeMercanciaEntregada" } ] }, provider_code: "2341702036", payment_modality: "02", coverage: "01", contract_number: "c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c", policy_number: "", copayment: 0, moderator_fee: 0, shared_payments: 0, advance: 0, justification_without_contract: "" }, customer: { country_id: "45", city_id: "836", identity_document_id: "1", type_organization_id: 2, tax_regime_id: 2, tax_level_id: 5, company_name: "LOPEZ GOMEZ LEWIS OSWALDO", dni: "1063279307", mobile: "3108435423", email: "cliente4@ejemplo.com", address: "Calle 64 #1823", postal_code: "661002" }, lines: [ { invoiced_quantity: "2", quantity_units_id: "1093", line_extension_amount: "100.00", free_of_charge_indicator: false, description: "TIJERA NECROPSIA AVES", code: "HMT83", type_item_identifications_id: "4", reference_price_id: "1", price_amount: "50", base_quantity: "2", tax_totals: [ { tax_id: "1", tax_amount: 19, taxable_amount: 100, percent: 19 } ] }, { invoiced_quantity: "2", quantity_units_id: "1093", line_extension_amount: "100.00", free_of_charge_indicator: false, description: "TIJERA NECROPSIA AVES 2", code: "HMT84", type_item_identifications_id: "4", reference_price_id: "1", price_amount: "50", base_quantity: "2", tax_totals: [ { tax_id: "1", tax_amount: 5, taxable_amount: 100, percent: 5 } ] } ], legal_monetary_totals: { line_extension_amount: "200.00", tax_exclusive_amount: "200.00", tax_inclusive_amount: "224.00", payable_amount: 224.00 }, tax_totals: [ { tax_id: "1", tax_amount: 19, taxable_amount: 100, percent: 19 }, { tax_id: "1", tax_amount: 5, taxable_amount: 100, percent: 5 } ] }; const response = await axios.post(`${url}/notes/credit`, payload, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); console.log(response.data); ``` **PHP (Guzzle)** ```php use GuzzleHttp\Client; $client = new Client(); $response = $client->post("{$url}/notes/credit", [ 'headers' => [ 'Authorization' => "Bearer {$token}", 'Content-Type' => 'application/json', ], 'json' => [ 'resolution_number' => '18763005880468', 'prefix' => 'NC', 'notes' => 'Documento anulado por errores en datos del cliente', 'document_number' => '7061', 'operation_type_id' => 12, 'type_document_id' => 5, 'graphic_representation' => 1, 'send_email' => 1, 'document_signature' => [ 'cashier' => 'Nombre del cajero(a)', 'seller' => 'Nombre del vendedor(a)' ], 'discrepancy_response' => [ 'reference_id' => 'LZT6004', 'response_id' => '2' ], 'billing_reference' => [ 'number' => 'LZT6004', 'date' => '2026-08-16', 'uuid' => 'ba87dd080aefd0558251633ceae95bb6cfe99b18625370c098edc73765bd65914a0447288ab72d1e24913e36813a4289' ], 'order_reference' => [ 'reference_number' => '4502878541', 'reference_date' => '2026-05-02' ], 'payments' => [ [ 'payment_method_id' => 1, 'means_payment_id' => 10, 'value_paid' => '224.00' ] ], 'health' => [ 'operation_type' => 'SS-CUDE', 'provider_code' => '2341702036', 'payment_modality' => '02', 'coverage' => '01', 'contract_number' => 'c12bab98915513f00c50e7efdde112763c65b421afcc1f3cea76302e1c80e21c', 'policy_number' => '', 'copayment' => 0, 'moderator_fee' => 0, 'shared_payments' => 0, 'advance' => 0, 'justification_without_contract' => '', 'invoice_period' => [ 'start_date' => '2026-07-01', 'start_time' => '23:59:59', 'end_date' => '2026-07-30', 'end_time' => '23:59:59' ], 'download_attachments' => [ 'url' => 'www.ips-1.com.co', 'arguments' => [ ['name' => 'excelFile', 'value' => 'a1b2c3.xlsx'], ['name' => 'txtFile', 'value' => 'a1b2c3.txt'] ] ], 'document_delivery' => [ 'ws' => 'https://ws4erp.ips-987.com.co/WcfRecibiendoDocs4ERP.svc?wsdl', 'arguments' => [ ['name' => 'Método-1', 'value' => 'ClienteEntregaAcuseDeReciboDeFEV-VP'], ['name' => 'Método-2', 'value' => 'ClienteEntregaConstanciaDeMercanciaEntregada'] ] ] ], 'customer' => [ 'country_id' => '45', 'city_id' => '836', 'identity_document_id' => '1', 'type_organization_id' => 2, 'tax_regime_id' => 2, 'tax_level_id' => 5, 'company_name' => 'LOPEZ GOMEZ LEWIS OSWALDO', 'dni' => '1063279307', 'mobile' => '3108435423', 'email' => 'cliente4@ejemplo.com', 'address' => 'Calle 64 #1823', 'postal_code' => '661002' ], 'lines' => [ [ 'invoiced_quantity' => '2', 'quantity_units_id' => '1093', 'line_extension_amount' => '100.00', 'free_of_charge_indicator' => false, 'description' => 'TIJERA NECROPSIA AVES', 'code' => 'HMT83', 'type_item_identifications_id' => '4', 'reference_price_id' => '1', 'price_amount' => '50', 'base_quantity' => '2', 'tax_totals' => [ [ 'tax_id' => '1', 'tax_amount' => 19, 'taxable_amount' => 100, 'percent' => 19 ] ] ], [ 'invoiced_quantity' => '2', 'quantity_units_id' => '1093', 'line_extension_amount' => '100.00', 'free_of_charge_indicator' => false, 'description' => 'TIJERA NECROPSIA AVES 2', 'code' => 'HMT84', 'type_item_identifications_id' => '4', 'reference_price_id' => '1', 'price_amount' => '50', 'base_quantity' => '2', 'tax_totals' => [ [ 'tax_id' => '1', 'tax_amount' => 5, 'taxable_amount' => 100, 'percent' => 5 ] ] ] ], 'legal_monetary_totals' => [ 'line_extension_amount' => '200.00', 'tax_exclusive_amount' => '200.00', 'tax_inclusive_amount' => '224.00', 'payable_amount' => 224.00 ], 'tax_totals' => [ [ 'tax_id' => '1', 'tax_amount' => 19, 'taxable_amount' => 100, 'percent' => 19 ], [ 'tax_id' => '1', 'tax_amount' => 5, 'taxable_amount' => 100, 'percent' => 5 ] ] ] ]); echo $response->getBody(); ``` --- ## 📋 Reglas de Validación para Nota Crédito en Salud - [ ] **Tipo de Operación y Documento:** `operation_type_id: 12` y `type_document_id: 5`. - [ ] **Operación Salud CUDE:** En el objeto `health`, el campo `operation_type` debe ser `"SS-CUDE"`. - [ ] **Referencia a Factura Obligatoria:** Debe incluir `billing_reference` con el número y CUFE exacto de la factura de salud afectada. - [ ] **Concepto de Corrección:** El campo `discrepancy_response.response_id` debe contener el código de concepto DIAN aplicable (ej. `2` = Anulación). --- # P.O.S Electrónico A continuación se muestra un ejemplo de un JSON que representa una factura de P.O.S Electrónico. Este JSON se puede utilizar para pruebas o para simular un punto de venta real. **Nota:** Este JSON es un ejemplo está de cuerdo de acuerdo a los últimos ajustes de la DIAN. ```json title="pos.json" { "resolution_number": "18764071164948", "prefix": "FPOS", "notes": "Nota del documento", "document_number": "229", "graphic_representation": 1, "send_email": 1, "operation_type_id": 1, "type_document_id": 20, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente4@ejemplo.com", "points": 0 }, "point_of_sale": { "cashier_name": "LEWIS OSWALDO LOPEZ GOMEZ", "terminal_number": "CJ001aB", "cashier_type": "Caja de apoyo", "sales_code": "POS01", "address": "Gilbarco Encore 4 L1 - Mangue ra 17 AC", "sub_total": "2000.10" }, "software_manufacturer": { "owner_name": "LEWIS LOPEZ GOMEZ", "company_name": "LOPEZSOFT SAS", "software_name": "SOFTWARE POS MATIAS APP" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # P.O.S consumidor final A continuación se muestra un ejemplo de un JSON que representa una factura de P.O.S Electrónico para consumidor final. Este JSON se puede utilizar para pruebas o para simular un punto de venta real. **Nota:** Este JSON es un ejemplo está de cuerdo de acuerdo a los últimos ajustes de la DIAN. ```json title="pos.json" { "resolution_number": "18764071164948", "prefix": "FPOS", "notes": "Nota del documento", "document_number": "231", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 20, "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "point_of_sale": { "cashier_name": "LEWIS LOPEZ", "terminal_number": "CJ001aB", "cashier_type": "Caja de apoyo", "sales_code": "POS01", "address": "Gilbarco Encore 4 L1 - Mangue ra 17 AC", "sub_total": "2000.10" }, "software_manufacturer": { "owner_name": "LEWIS LOPEZ GOMEZ", "company_name": "LOPEZSOFT SAS", "software_name": "SOFTWARE POS MATIAS APP" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # P.O.S con descuento A continuación se muestra un ejemplo de un JSON que representa una factura de P.O.S Electrónico para consumidor final. Este JSON se puede utilizar para pruebas o para simular un punto de venta real. **Nota:** Este JSON es un ejemplo está de cuerdo de acuerdo a los últimos ajustes de la DIAN. ```json title="pos.json" { "resolution_number": "18764071667638", "prefix": "FPOS", "date": "2024-06-07", "time": "12:55:32", "notes": "", "document_number": "14", "graphic_representation": 1, "send_email": 1, "operation_type_id": 1, "type_document_id": 20, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "59.50" } ], "software_manufacturer": { "owner_name": "LEWIS LOPEZ GOMEZ", "company_name": "LOPEZSOFT SAS", "software_name": "SOFTWARE POS MATIAS APP" }, "lines": [ { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": "50", "free_of_charge_indicator": false, "description": "producto alimento", "code": "11952875", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "100", "base_quantity": 1, "tax_totals": [ { "tax_id": "1", "tax_amount": 9.5, "taxable_amount": 50, "percent": 19 } ], "allowance_charges": [ { "charge_indicator": false, "allowance_charge_reason": "Descuento por promoción", "base_amount": "100", "amount": "50" } ] } ], "legal_monetary_totals": { "line_extension_amount": "50", "tax_exclusive_amount": "50", "tax_inclusive_amount": "59.5", "total_allowance": 0, "payable_amount": 59.5 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 9.5, "taxable_amount": 50, "percent": 19 } ], "point_of_sale": { "cashier_name": "Veterinario demo2", "terminal_number": "CJ001aB", "cashier_type": "Caja de apoyo", "sales_code": "POS1", "address": "Storni 6278", "sub_total": 59.50 } } ``` --- # Nota Débito P.O.S. A continuación se muestra un ejemplo de un JSON que representa una nota débito P.O.S. Este JSON se puede utilizar para pruebas o para simular una nota débito real. ```json title="pos-debit-note.json" { "resolution_number": "18760000001", "date": "2024-01-24", "time": "14:49:36", "document_number": "1", "operation_type_id": 14, "type_document_id": 93, "graphic_representation": 1, "send_email": 1, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "95000.00" }], "customer": { "company_name": "Santiago Arango", "dni": "1152440359", "points": 0 }, "discrepancy_response": { "reference_id": "EPOS2", "response_id": "9" }, "billing_reference": { "number": "EPOS2", "date": "2023-12-22", "uuid": "b1b5d93a2918407a2ef0048ed3092e5d96c94f73db178779463f202f8c52dd53ef5b9888d804d4b609521b1d031aea39" }, "lines": [ { "invoiced_quantity": 1, "quantity_units_id": "1093", "line_extension_amount": 79831.93, "free_of_charge_indicator": false, "description": "STILOGRAFOLAMYALSTAR025M", "code": "4014519428527", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 95000, "base_quantity": 1, "tax_totals": [ { "tax_id": 1, "tax_amount": 15168.07, "taxable_amount": 79831.93, "percent": "19" } ] } ], "legal_monetary_totals": { "line_extension_amount": 79831.93, "tax_exclusive_amount": 79831.93, "tax_inclusive_amount": 95000, "total_charges": 0, "pre_paid_amount": 0, "total_allowance": 0, "payable_amount": 95000 }, "tax_totals": [ { "tax_id": 1, "tax_amount": 15168.07, "taxable_amount": 79831.93, "percent": "19" } ] } ``` --- # Nota Crédito P.O.S A continuación se muestra un ejemplo de un JSON que representa una nota crédito P.O.S. Este JSON se puede utilizar para pruebas o para simular una nota crédito real. ```json title="pos-credit-note.json" { "resolution_number": "18760000001", "notes": "Nota del documento", "document_number": "26", "operation_type_id": 11, "type_document_id": 94, "graphic_representation": 1, "send_email": 1, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" }], "document_signature": { "cashier": "Nombre del cajero(a)", "seller": "Nombre del vendedor(a)" }, "customer": { "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente4@ejemplo.com", "points": 0 }, "discrepancy_response": { "reference_id": "FPOS229", "response_id": "16" }, "billing_reference": { "number": "FPOS229", "date": "2025-03-29", "uuid": "4d91fa3e2d5a1835c6e3432c1eaa9dbbc9140085f19607680edbd57c7d6b7a86295e4177b3ac823a069684b2f702e216", "scheme_name": "CUDE-SHA384" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "224.00", "payable_amount": 224.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 }, { "tax_id": "1", "tax_amount": 5, "taxable_amount": 100, "percent": 5 } ] } ``` --- # Documento soporte residente A continuación se muestra un ejemplo de un JSON que representa un documento soporte. Este JSON se puede utilizar para pruebas o para simular un documento soporte real. ## Residente ```json title="support-document-resident.json" { "resolution_number": "18764090837763", "notes": "Nota del documento", "document_number": "100", "operation_type_id": 9, "type_document_id": 11, "graphic_representation": 0, "currency_id": 272, "send_email": 1, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "230525.00" }], "customer": { "country_id": "45", "city_id": "149", "identity_document_id": "3", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435431", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "3", "quantity_units_id": "1093", "line_extension_amount": "81600", "free_of_charge_indicator": false, "description": "Hunters Mini Tumaco 82%", "code": "HMT82", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "27200", "base_quantity": "3", "invoice_period": { "start_date": "2022-08-30", "description_code": 1 } }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100000", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50000", "base_quantity": "2", "invoice_period": { "start_date": "2022-08-30", "description_code": 1 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19000, "taxable_amount": 100000, "percent": 19 } ] }, { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "28500", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT81", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "28500", "base_quantity": "1", "invoice_period": { "start_date": "2022-08-30", "description_code": 1 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 1425, "taxable_amount": 28500, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "210100", "tax_exclusive_amount": "128500", "tax_inclusive_amount": "230525", "payable_amount": "230525" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 1425, "taxable_amount": 28500, "percent": 5 }, { "tax_id": "1", "tax_amount": 19000, "taxable_amount": 100000, "percent": 19 } ] } ``` --- # Documento soporte no residente A continuación se muestra un ejemplo de un JSON que representa un documento soporte. Este JSON se puede utilizar para pruebas o para simular un documento soporte real. ## No residente ```json title="support-document-non-residents.json" { "resolution_number": "18764090837763", "document_number": "106", "operation_type_id": 10, "type_document_id": 11, "graphic_representation": 1, "currency_id": 272, "payments": [ { "payment_method_id": "1", "means_payment_id": "10", "value_paid": "790329.00" } ], "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UND", "line_extension_amount": "790329.00", "free_of_charge_indicator": false, "description": "COMPRA KRYTOX POR CHEMOURS GPL 205 GRASA, PFPE PURO", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 790329.00, "base_quantity": 1.00, "invoice_period": { "start_date": "2025-03-01", "description_code": "1" } } ], "customer": { "country_id": "239", "identity_document_id": "10", "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "legal_monetary_totals": { "line_extension_amount": "790329.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "790329.00", "payable_amount": "790329.00" } } ``` --- # Notas de ajuste documento soporte residente A continuación se muestra un ejemplo de un JSON que representa un documento soporte de nota de ajuste. Este JSON se puede utilizar para pruebas o para simular un documento soporte real. ## Residente ```json title="adjustment-note.json" { "resolution_number": "18760000001", "date": "2024-01-12", "time": "22:46:53", "notes": "Nota del documento", "document_number": "3", "operation_type_id": 1, "type_document_id": 15, "graphic_representation": 1, "send_email": 1, "currency_id": 272, "payments": [{ "payment_method_id": 1, "means_payment_id": 10, "value_paid": "230525.00" }], "discrepancy_response": { "reference_id": "DS10", "response_id": "11", "description": "Anulación porque el documento se generó a vendedor incorecto" }, "billing_reference": { "number": "DS10", "date": "2024-01-11", "uuid": "4d3aa5a3d69e674adb0b29b8b7c091682e5ce5d47a35c7b7a24442e8c93eaeb8a7e84c4b012ce76f04fb9dd692ef7168", "scheme_name": "CUDS-SHA384" }, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "3", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "Santiago Arango", "dni": "1152440359", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Direccion residencial", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "3", "quantity_units_id": "1093", "line_extension_amount": "81600", "free_of_charge_indicator": false, "description": "Hunters Mini Tumaco 82%", "code": "HMT82", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "27200", "base_quantity": "3", "invoice_period": { "start_date": "2022-08-30", "description_code": 1 } }, { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100000", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES 2", "code": "HMT84", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50000", "base_quantity": "2", "invoice_period": { "start_date": "2022-08-30", "description_code": 1 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19000, "taxable_amount": 100000, "percent": 19 } ] }, { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "28500", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT81", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "28500", "base_quantity": "1", "invoice_period": { "start_date": "2022-08-30", "description_code": 1 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 1425, "taxable_amount": 28500, "percent": 5 } ] } ], "legal_monetary_totals": { "line_extension_amount": "210100", "tax_exclusive_amount": "128500", "tax_inclusive_amount": "230525", "payable_amount": "230525" }, "tax_totals": [ { "tax_id": "1", "tax_amount": 1425, "taxable_amount": 28500, "percent": 5 }, { "tax_id": "1", "tax_amount": 19000, "taxable_amount": 100000, "percent": 19 } ] } ``` --- # Notas de ajuste documento soporte no residente A continuación se muestra un ejemplo de un JSON que representa un documento soporte de nota de ajuste. Este JSON se puede utilizar para pruebas o para simular un documento soporte real. ## No residente ```json title="adjustment-note.json" { "resolution_number": "18760000001", "notes": "Nota del documento", "document_number": "71", "operation_type_id": 10, "type_document_id": 15, "graphic_representation": 1, "send_email": 1, "currency_id": 272, "discrepancy_response": { "reference_id": "DS106", "response_id": "11", "description": "Anulación porque el documento se generó a vendedor incorecto" }, "billing_reference": { "number": "DS106", "date": "2025-04-13", "uuid": "7afa7f2dcf133ca8be44853dc5506b48b9860e86d585dc56ac0692fb65281f2d6a51dc7fd6d1fa44d78e9f5ed2dc714e", "scheme_name": "CUDS-SHA384" }, "payments": [ { "payment_method_id": "1", "means_payment_id": "10", "value_paid": "790329.00" } ], "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "um": "UND", "line_extension_amount": "790329.00", "free_of_charge_indicator": false, "description": "COMPRA KRYTOX POR CHEMOURS GPL 205 GRASA, PFPE PURO", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": 790329.00, "base_quantity": 1.00, "invoice_period": { "start_date": "2025-03-01", "description_code": "1" } } ], "customer": { "country_id": "239", "identity_document_id": "10", "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "legal_monetary_totals": { "line_extension_amount": "790329.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "790329.00", "payable_amount": "790329.00" } } ``` --- # Campos de la nómina La nómina electrónica es un documento que contiene información detallada de los pagos y deducciones que se le realizan a un trabajador. En este documento se incluyen datos como el nombre del trabajador, el periodo de pago, el salario, las deducciones, entre otros. A continuación se describen los campos que componen la nómina electrónica. ## JSON de ejemplo ```json title="payroll.json" { "resolution_number": "18760000001", "document_number": "27", "generation_city_id" : "836", "worker_code": "1063279307", "novelty": false, "pay_day": "2023-09-30", "period": { "date_entry": "2017-01-01", "departure_date": null, "settlement_start_date": "2023-09-01", "settlement_end_date": "2023-09-30", "time_worked": "30", "generation_date": "2023-09-24" }, "general_information": { "generation_date": "2023-09-24", "generation_time": "10:00:00", "period_id": "5", "currency_id": "272", "trm": "0" }, "notes": "", "employee": { "worker_type_id": 1, "worker_subtype_id": 1, "high_risk_pension": "false", "identity_document_id": "1", "document_number": "1063279307", "first_surname": "LOPEZ", "second_surname": "GOMES", "first_name": "LEWIS", "other_names": "OSWALDO", "working_country_id": 45, "work_city_id": 149, "work_address": "Calle 64 #1631 La Capilla Dosquebradas", "integral_salary": "false", "contract_type_id": 1, "salary": "1200000", "worker_code": "1063279307" }, "payment": { "payment_method_id": "1", "means_payment_id": "31", "bank": "BANCOLOMBIA", "account_type": "AHORROS", "account_number": "96645544844" }, "earn": { "basic": { "worked_days": "30", "salary_worked": "1200000" }, "transport": { "transportation_assistance": "106454", "viatic_maintenance": "0.0", "viatic_non_salary_maintenance": "0.0" }, "HEDs": [ { "start_time": null, "final_hour": null, "amount": "20", "percentage": "25.00", "payment": "125000" } ], "HENs": [ { "start_time": "2021-12-31T00:00:00", "final_hour": "2021-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HRNs": [ { "start_time": "2021-12-31T00:00:00", "final_hour": "2021-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HEDDFs": [ { "start_time": "2021-12-31T00:00:00", "final_hour": "2021-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HRDDFs": [ { "start_time": "2021-12-31T00:00:00", "final_hour": "2021-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HENDFs": [ { "start_time": "2021-12-31T00:00:00", "final_hour": "2021-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HRNDFs": [ { "start_time": "2021-12-31T00:00:00", "final_hour": "2021-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "vacations": { "common": { "start_date": "2021-12-31", "final_date": "2021-12-31", "amount": "0", "payment": "0.00" }, "paid": { "amount": "0", "payment": "0.00" } }, "bonus": { "amount": "0", "payment": "0.00", "non_salary_payment": "0.00" }, "cesantias": { "payment": "0", "percentage": "0.00", "interest_payment": "0.00" }, "incapacity": [ { "start_date": "2021-12-31", "final_date": "2021-12-31", "amount": "0", "type_id": 1, "payment": "0.00" } ], "licenses": { "licenseMP": { "start_date": "2021-12-31", "final_date": "2021-12-31", "amount": "0", "payment": "0.00" }, "licenseR": { "start_date": "2021-12-31", "final_date": "2021-12-31", "amount": "0", "payment": "0.00" }, "licenseNR": { "start_date": "2021-12-31", "final_date": "2021-12-31", "amount": "0" } }, "bonuses": [ { "bonusS": "0.00", "bonusNS": "0.00" } ], "assistances": [ { "assistanceS": "0.00", "assistanceNS": "0.00" } ], "legal_strikes": [ { "start_date": "2021-12-31", "final_date": "2021-12-31", "amount": "0" } ], "other_concepts": [ { "description": "Otros conceptos", "conceptS": "0.00", "conceptNS": "4000.00" } ], "compensations": [ { "compensationO": "0.00", "compensationE": "0.00" } ], "bondEPCTVs": [ { "paymentS": "0.00", "paymentNS": "0.00", "payment_foodS": "0.00", "payment_foodNS": "0.00" } ], "commissions": [ { "commission": "0.00" } ], "payments_third_party": [ { "payment_third_party": "0.00" } ], "advances": [ { "advance": "0.00" } ], "endowment": "0.00", "sustaining_support": "0.00", "teleworking": "0.00", "withdrawal_bonus": "0.00", "indemnification": "0.00", "refund": "0.00" }, "deductions": { "health": { "percentage": "4", "deduction": "43700" }, "pension_fund": { "percentage": "4", "deduction": "43700" }, "fundSP": { "percentage": "0.00", "deduction": "0.00", "percentageSub": "0.00", "deductionSub": "0.00" }, "trade_union": [ { "percentage": "0.00", "deduction": "0.00" } ], "sanctions": [ { "sanctionPublic": "0.00", "sanctionPriv": "0.00" } ], "libranzas": [ { "description": "", "deduction": "0.00" } ], "third_party_payment": [ { "third_party_pay": "90000.00" } ], "advances": [ { "advance": "0.00" } ], "other_deductions": [ { "other_deduction": "0.00" } ], "voluntary_pension": "0.00", "retefuente": "0.00", "afc": "0.00", "cooperative": "0.00", "tax_embargo": "0.00", "complementary_plan": "0.00", "education": "0.00", "refund": "0.00", "debt": "0.00" }, "rounding": "46", "total_earned": "1325000", "deductions_total": "97400", "total_voucher": "1334054" } ``` # Diccionario de Datos ## Campos Principales - `resolution_number`: STRING. Número de la resolución. - `document_number`: STRING. Número de documento asociado. - `generation_city_id`: STRING. ID de la ciudad donde se genera el documento. - `worker_code`: STRING. Código único del trabajador. - `novelty`: BOOLEAN. Indica si hay novedades. - `pay_day`: DATE. Fecha de pago. - `notes`: STRING. Notas adicionales. - `rounding`: STRING. Redondeo aplicado. - `total_earned`: STRING. Total ganado. - `deductions_total`: STRING. Total deducción. - `total_voucher`: STRING. Total del comprobante. ## Periodo - `date_entry`: DATE. Fecha de entrada. - `departure_date`: DATE. Fecha de salida, puede ser nula. - `settlement_start_date`: DATE. Inicio del período de liquidación. - `settlement_end_date`: DATE. Fin del período de liquidación. - `time_worked`: STRING. Tiempo trabajado (días). - `generation_date`: DATE. Fecha de generación del documento. ## Información General - `generation_date`: DATE. Fecha de generación del documento. - `generation_time`: TIME. Hora de generación del documento. - `period_id`: STRING. ID del período. - `currency_id`: STRING. ID de la moneda. - `trm`: STRING. Tasa representativa del mercado. ## Empleado - `worker_type_id`: INTEGER. Tipo de trabajador. - `worker_subtype_id`: INTEGER. Subtipo de trabajador. - `high_risk_pension`: BOOLEAN. Indica si es pensión de alto riesgo. - `identity_document_id`: STRING. ID del documento de identidad. - `document_number`: STRING. Número de documento del trabajador. - `first_surname`: STRING. Primer apellido. - `second_surname`: STRING. Segundo apellido. - `first_name`: STRING. Primer nombre. - `other_names`: STRING. Otros nombres. - `working_country_id`: INTEGER. ID del país de trabajo. - `work_city_id`: INTEGER. ID de la ciudad de trabajo. - `work_address`: STRING. Dirección de trabajo. - `integral_salary`: BOOLEAN. Indica si el salario es integral. - `contract_type_id`: INTEGER. Tipo de contrato. - `salary`: STRING. Salario. ## Pago - `payment_method_id`: STRING. ID del método de pago. - `means_payment_id`: STRING. ID del medio de pago. - `bank`: STRING. Banco. - `account_type`: STRING. Tipo de cuenta. - `account_number`: STRING. Número de cuenta. ## Devengado (`earn`) ### Básico (`basic`) - `worked_days`: STRING. Días trabajados en el período. - `salary_worked`: STRING. Salario correspondiente a los días trabajados. ### Transporte (`transport`) - `transportation_assistance`: STRING. Asistencia de transporte. - `viatic_maintenance`: STRING. Viáticos de mantenimiento. - `viatic_non_salary_maintenance`: STRING. Viáticos de mantenimiento no salariales. ### Horas Extras Diurnas (`HEDs`) - `start_time`: TIME (nullable). Hora de inicio de las horas extras diurnas, puede ser nulo. - `final_hour`: TIME (nullable). Hora final de las horas extras diurnas, puede ser nulo. - `amount`: STRING. Cantidad de horas extras diurnas trabajadas. - `percentage`: STRING. Porcentaje aplicado a las horas extras diurnas. - `payment`: STRING. Pago correspondiente a las horas extras diurnas. ### Horas Extras Nocturnas (`HENs`), Horas de Recargo Nocturno (`HRNs`), Horas Extras Diurnas en Día Festivo (`HEDDFs`), etc. - Cada tipo de hora extra o recargo tiene campos para `start_time`, `final_hour`, `amount`, `percentage`, y `payment`, adaptándose según el tipo específico. ### Vacaciones (`vacations`) - `common`: OBJETO. Vacaciones comunes con `start_date`, `final_date`, `amount`, y `payment`. - Descripción los campos - `start_date`: Fecha de inicio de las vacaciones. - `final_date`: Fecha final de las vacaciones. - `amount`: Cantidad de días de vacaciones. - `payment`: Pago correspondiente a las vacaciones. - `paid`: OBJETO. Vacaciones pagadas con `amount` y `payment`. - Descripción los campos - `amount`: Cantidad de días de vacaciones pagadas. - `payment`: Pago correspondiente a las vacaciones pagadas. - `paid`: OBJETO. Vacaciones pagadas con `amount` y `payment`. - ### Ejemplo de vacaciones: ```json "vacations": { "common": { "start_date": "2023-01-01", "final_date": "2023-01-10", "amount": "10", "payment": "1000" }, "paid": { "amount": "2000", "payment": "2000" } } ``` ### Primas (`bonus`) - `amount`: STRING. Cantidad de la prima. - `payment`: STRING. Pago de la prima. - `non_salary_payment`: STRING. Pago de la prima no salarial. ### Cesantías (`cesantias`) - `payment`: STRING. Pago de las cesantías. - `percentage`: STRING. Porcentaje aplicado a las cesantías. - `interest_payment`: STRING. Pago de intereses sobre las cesantías. ### Incapacidades (`incapacity`) - Lista de incapacidades, cada una con `start_date`, `final_date`, `amount`, `type_id`, y `payment`. ### Licencias (`licenses`) - Incluye licencias por maternidad (`licenseMP`), recreativas (`licenseR`), y no remuneradas (`licenseNR`), cada una con `start_date`, `final_date`, `amount`, y `payment` (cuando aplique). ### Otros Conceptos de Ganancias - Detalles de bonificaciones especiales (`bonuses`), asistencias (`assistances`), huelgas legales (`legal_strikes`), otros conceptos (`other_concepts`), compensaciones (`compensations`), bonificaciones por EPCTV (`bondEPCTVs`), comisiones (`commissions`), pagos a terceros (`payments_third_party`), anticipos (`advances`), dotación (`endowment`), apoyo sostenimiento (`sustaining_support`), teletrabajo (`teleworking`), bonificación por retiro (`withdrawal_bonus`), indemnizaciones (`indemnification`), reembolsos (`refund`), especificando los campos relevantes para cada uno. ## Deducciones (`deductions`) ### Salud (`health`) - `percentage`: STRING. Porcentaje de deducción por concepto de salud. - `deduction`: STRING. Monto deducido por salud. ### Fondo de Pensiones (`pension_fund`) - `percentage`: STRING. Porcentaje de deducción por fondo de pensiones. - `deduction`: STRING. Monto deducido por fondo de pensiones. ### Fondo de Solidaridad Pensional (`fundSP`) - `percentage`: STRING. Porcentaje de contribución al fondo de solidaridad pensional. - `deduction`: STRING. Monto deducido por fondo de solidaridad pensional. - `percentageSub`: STRING. Porcentaje de contribución subsidiada al fondo de solidaridad pensional. - `deductionSub`: STRING. Monto deducido por contribución subsidiada. ### Sindicato (`trade_union`) - `percentage`: STRING. Porcentaje de deducción por sindicato. - `deduction`: STRING. Monto deducido por sindicato. ### Sanciones (`sanctions`) - `sanctionPublic`: STRING. Sanciones públicas. - `sanctionPriv`: STRING. Sanciones privadas. ### Libranzas (`libranzas`) - `description`: STRING. Descripción de la libranza. - `deduction`: STRING. Monto deducido por libranza. ### Pago a Terceros (`third_party_payment`) - `third_party_pay`: STRING. Pago realizado a terceros. ### Anticipos (`advances`) - `advance`: STRING. Monto de los anticipos proporcionados. ### Otras Deducciones (`other_deductions`) - `other_deduction`: STRING. Monto de otras deducciones no especificadas anteriormente. ### Pension Voluntaria (`voluntary_pension`) - `voluntary_pension`: STRING. Monto deducido por concepto de pensión voluntaria. ### Retención en la Fuente (`retefuente`) - `retefuente`: STRING. Monto deducido por retención en la fuente. ### AFC (`afc`) - `afc`: STRING. Monto deducido por cuentas de ahorro para el fomento de la construcción (AFC). ### Cooperativa (`cooperative`) - `cooperative`: STRING. Monto deducido por aportes a cooperativas. ### Embargo Fiscal (`tax_embargo`) - `tax_embargo`: STRING. Monto deducido por embargos fiscales. ### Plan Complementario (`complementary_plan`) - `complementary_plan`: STRING. Monto deducido por plan complementario de salud. ### Educación (`education`) - `education`: STRING. Monto deducido por concepto de educación. ### Reembolso (`refund`) - `refund`: STRING. Monto de reembolsos realizados al empleado. ### Deuda (`debt`) - `debt`: STRING. Monto deducido por deudas con la empresa. ## Totales del Documento - `total_earned`: STRING. Total de ingresos ganados por el empleado durante el período. - `deductions_total`: STRING. Total de deducciones aplicadas al pago del empleado. - `total_voucher`: STRING. Monto final después de aplicar ingresos y deducciones. --- # Campos de la nómina de reemplazo Los campos de la nómina de reemplazo son los campos que se utilizan para calcular la nómina de un empleado que ha sido reemplazado por otro empleado. Estos campos se utilizan para calcular la nómina de un empleado que ha sido reemplazado por otro empleado. Los campos de la nómina de reemplazo son los siguientes: A continuación se describen los campos que componen la nómina electrónica. ## JSON de ejemplo ```json title="payroll-replace.json" { "resolution_number": "18760000002", "document_number": "10", "generation_city_id" : "836", "worker_code": "1063279307", "novelty": false, "pay_day": "2022-08-30", "replacing_predecessor": { "number": "NE27", "cune": "fa6e59a1d6f69b1d4e37d15f7485744ce5f74627496e91fba0058aacb52d580f6482d89faa34c8861a6e29df856f4dfa", "generation_date": "2022-08-28" }, "period": { "date_entry": "2017-01-01", "departure_date": null, "settlement_start_date": "2022-08-01", "settlement_end_date": "2022-08-30", "time_worked": "30", "generation_date": "2022-09-24" }, "general_information": { "generation_date": "2022-09-24", "generation_time": "10:00:00", "period_id": "5", "currency_id": "272", "trm": "0" }, "notes": "", "employee": { "worker_type_id": 1, "worker_subtype_id": 1, "high_risk_pension": "false", "identity_document_id": "1", "document_number": "1063279307", "first_surname": "LOPEZ", "second_surname": "GOMES", "first_name": "LEWIS", "other_names": "OSWALDO", "working_country_id": 45, "work_city_id": 149, "work_address": "Calle 64 #1631 La Capilla Dosquebradas", "integral_salary": "false", "contract_type_id": 1, "salary": "1200000", "worker_code": "1063279307" }, "payment": { "payment_method_id": "1", "means_payment_id": "31", "bank": "BANCOLOMBIA", "account_type": "AHORROS", "account_number": "96645544844" }, "earn": { "basic": { "worked_days": "30", "salary_worked": "1200000" }, "transport": { "transportation_assistance": "106454", "viatic_maintenance": "0.0", "viatic_non_salary_maintenance": "0.0" }, "HEDs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HENs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HRNs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HEDDFs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HRDDFs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HENDFs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "HRNDFs": [ { "start_time": "2022-12-31T00:00:00", "final_hour": "2022-12-31T00:00:00", "amount": "0", "percentage": "0.00", "payment": "0.00" } ], "vacations": { "common": { "start_date": "2022-12-31", "final_date": "2022-12-31", "amount": "0", "payment": "0.00" }, "paid": { "amount": "0", "payment": "0.00" } }, "bonus": { "amount": "0", "payment": "0.00", "non_salary_payment": "0.00" }, "cesantias": { "payment": "0", "percentage": "0.00", "interest_payment": "0.00" }, "incapacity": [ { "start_date": "2022-12-31", "final_date": "2022-12-31", "amount": "0", "type_id": 1, "payment": "0.00" } ], "licenses": { "licenseMP": { "start_date": "2022-12-31", "final_date": "2022-12-31", "amount": "0", "payment": "0.00" }, "licenseR": { "start_date": "2022-12-31", "final_date": "2022-12-31", "amount": "0", "payment": "0.00" }, "licenseNR": { "start_date": "2022-12-31", "final_date": "2022-12-31", "amount": "0" } }, "bonuses": [ { "bonusS": "0.00", "bonusNS": "0.00" } ], "assistances": [ { "assistanceS": "0.00", "assistanceNS": "0.00" } ], "legal_strikes": [ { "start_date": "2022-12-31", "final_date": "2022-12-31", "amount": "0" } ], "other_concepts": [ { "description": "A", "conceptS": "0.00", "conceptNS": "0.00" } ], "compensations": [ { "compensationO": "0.00", "compensationE": "0.00" } ], "bondEPCTVs": [ { "paymentS": "0.00", "paymentNS": "0.00", "payment_foodS": "0.00", "payment_foodNS": "0.00" } ], "commissions": [ { "commission": "0.00" } ], "payments_third_party": [ { "payment_third_party": "0.00" } ], "advances": [ { "advance": "0.00" } ], "endowment": "0.00", "sustaining_support": "0.00", "teleworking": "0.00", "withdrawal_bonus": "0.00", "indemnification": "0.00", "refund": "0.00" }, "deductions": { "health": { "percentage": "4", "deduction": "43700" }, "pension_fund": { "percentage": "4", "deduction": "43700" }, "fundSP": { "percentage": "0.00", "deduction": "0.00", "percentageSub": "0.00", "deductionSub": "0.00" }, "trade_union": [ { "percentage": "0.00", "deduction": "0.00" } ], "sanctions": [ { "sanctionPublic": "0.00", "sanctionPriv": "0.00" } ], "libranzas": [ { "description": "", "deduction": "0.00" } ], "third_party_payment": [ { "third_party_pay": "0.00" } ], "advances": [ { "advance": "0.00" } ], "other_deductions": [ { "other_deduction": "0.00" } ], "voluntary_pension": "0.00", "retefuente": "0.00", "afc": "0.00", "cooperative": "0.00", "tax_embargo": "0.00", "complementary_plan": "0.00", "education": "0.00", "refund": "0.00", "debt": "0.00" }, "rounding": "46", "total_earned": "1200000", "deductiones_total": "87400", "total_voucher": "1219100" } ``` # Diccionario de Datos ## Campos Principales - `resolution_number`: STRING. Número de la resolución. - `document_number`: STRING. Número de documento asociado. - `generation_city_id`: STRING. ID de la ciudad donde se genera el documento. - `worker_code`: STRING. Código único del trabajador. - `novelty`: BOOLEAN. Indica si hay novedades. - `pay_day`: DATE. Fecha de pago. - `notes`: STRING. Notas adicionales. - `rounding`: STRING. Redondeo aplicado. - `total_earned`: STRING. Total ganado. - `deductions_total`: STRING. Total deducción. - `total_voucher`: STRING. Total del comprobante. ## Reemplazo - `replacing_predecessor`: OBJETO. Información del predecesor reemplazado con `number`, `cune`, y `generation_date`. - `number`: STRING. Número del predecesor. - `cune`: STRING. Código único de nómina electrónica. - `generation_date`: DATE. Fecha de generación del documento. ## Periodo - `date_entry`: DATE. Fecha de entrada. - `departure_date`: DATE. Fecha de salida, puede ser nula. - `settlement_start_date`: DATE. Inicio del período de liquidación. - `settlement_end_date`: DATE. Fin del período de liquidación. - `time_worked`: STRING. Tiempo trabajado (días). - `generation_date`: DATE. Fecha de generación del documento. ## Información General - `generation_date`: DATE. Fecha de generación del documento. - `generation_time`: TIME. Hora de generación del documento. - `period_id`: STRING. ID del período. - `currency_id`: STRING. ID de la moneda. - `trm`: STRING. Tasa representativa del mercado. ## Empleado - `worker_type_id`: INTEGER. Tipo de trabajador. - `worker_subtype_id`: INTEGER. Subtipo de trabajador. - `high_risk_pension`: BOOLEAN. Indica si es pensión de alto riesgo. - `identity_document_id`: STRING. ID del documento de identidad. - `document_number`: STRING. Número de documento del trabajador. - `first_surname`: STRING. Primer apellido. - `second_surname`: STRING. Segundo apellido. - `first_name`: STRING. Primer nombre. - `other_names`: STRING. Otros nombres. - `working_country_id`: INTEGER. ID del país de trabajo. - `work_city_id`: INTEGER. ID de la ciudad de trabajo. - `work_address`: STRING. Dirección de trabajo. - `integral_salary`: BOOLEAN. Indica si el salario es integral. - `contract_type_id`: INTEGER. Tipo de contrato. - `salary`: STRING. Salario. ## Pago - `payment_method_id`: STRING. ID del método de pago. - `means_payment_id`: STRING. ID del medio de pago. - `bank`: STRING. Banco. - `account_type`: STRING. Tipo de cuenta. - `account_number`: STRING. Número de cuenta. ## Devengado (`earn`) ### Básico (`basic`) - `worked_days`: STRING. Días trabajados en el período. - `salary_worked`: STRING. Salario correspondiente a los días trabajados. ### Transporte (`transport`) - `transportation_assistance`: STRING. Asistencia de transporte. - `viatic_maintenance`: STRING. Viáticos de mantenimiento. - `viatic_non_salary_maintenance`: STRING. Viáticos de mantenimiento no salariales. ### Horas Extras Diurnas (`HEDs`) - `start_time`: TIME (nullable). Hora de inicio de las horas extras diurnas, puede ser nulo. - `final_hour`: TIME (nullable). Hora final de las horas extras diurnas, puede ser nulo. - `amount`: STRING. Cantidad de horas extras diurnas trabajadas. - `percentage`: STRING. Porcentaje aplicado a las horas extras diurnas. - `payment`: STRING. Pago correspondiente a las horas extras diurnas. ### Horas Extras Nocturnas (`HENs`), Horas de Recargo Nocturno (`HRNs`), Horas Extras Diurnas en Día Festivo (`HEDDFs`), etc. - Cada tipo de hora extra o recargo tiene campos para `start_time`, `final_hour`, `amount`, `percentage`, y `payment`, adaptándose según el tipo específico. ### Vacaciones (`vacations`) - `common`: OBJETO. Vacaciones comunes con `start_date`, `final_date`, `amount`, y `payment`. - `paid`: OBJETO. Vacaciones pagadas con `amount` y `payment`. ### Primas (`bonus`) - `amount`: STRING. Cantidad de la prima. - `payment`: STRING. Pago de la prima. - `non_salary_payment`: STRING. Pago de la prima no salarial. ### Cesantías (`cesantias`) - `payment`: STRING. Pago de las cesantías. - `percentage`: STRING. Porcentaje aplicado a las cesantías. - `interest_payment`: STRING. Pago de intereses sobre las cesantías. ### Incapacidades (`incapacity`) - Lista de incapacidades, cada una con `start_date`, `final_date`, `amount`, `type_id`, y `payment`. ### Licencias (`licenses`) - Incluye licencias por maternidad (`licenseMP`), recreativas (`licenseR`), y no remuneradas (`licenseNR`), cada una con `start_date`, `final_date`, `amount`, y `payment` (cuando aplique). ### Otros Conceptos de Ganancias - Detalles de bonificaciones especiales (`bonuses`), asistencias (`assistances`), huelgas legales (`legal_strikes`), otros conceptos (`other_concepts`), compensaciones (`compensations`), bonificaciones por EPCTV (`bondEPCTVs`), comisiones (`commissions`), pagos a terceros (`payments_third_party`), anticipos (`advances`), dotación (`endowment`), apoyo sostenimiento (`sustaining_support`), teletrabajo (`teleworking`), bonificación por retiro (`withdrawal_bonus`), indemnizaciones (`indemnification`), reembolsos (`refund`), especificando los campos relevantes para cada uno. ## Deducciones (`deductions`) ### Salud (`health`) - `percentage`: STRING. Porcentaje de deducción por concepto de salud. - `deduction`: STRING. Monto deducido por salud. ### Fondo de Pensiones (`pension_fund`) - `percentage`: STRING. Porcentaje de deducción por fondo de pensiones. - `deduction`: STRING. Monto deducido por fondo de pensiones. ### Fondo de Solidaridad Pensional (`fundSP`) - `percentage`: STRING. Porcentaje de contribución al fondo de solidaridad pensional. - `deduction`: STRING. Monto deducido por fondo de solidaridad pensional. - `percentageSub`: STRING. Porcentaje de contribución subsidiada al fondo de solidaridad pensional. - `deductionSub`: STRING. Monto deducido por contribución subsidiada. ### Sindicato (`trade_union`) - `percentage`: STRING. Porcentaje de deducción por sindicato. - `deduction`: STRING. Monto deducido por sindicato. ### Sanciones (`sanctions`) - `sanctionPublic`: STRING. Sanciones públicas. - `sanctionPriv`: STRING. Sanciones privadas. ### Libranzas (`libranzas`) - `description`: STRING. Descripción de la libranza. - `deduction`: STRING. Monto deducido por libranza. ### Pago a Terceros (`third_party_payment`) - `third_party_pay`: STRING. Pago realizado a terceros. ### Anticipos (`advances`) - `advance`: STRING. Monto de los anticipos proporcionados. ### Otras Deducciones (`other_deductions`) - `other_deduction`: STRING. Monto de otras deducciones no especificadas anteriormente. ### Pension Voluntaria (`voluntary_pension`) - `voluntary_pension`: STRING. Monto deducido por concepto de pensión voluntaria. ### Retención en la Fuente (`retefuente`) - `retefuente`: STRING. Monto deducido por retención en la fuente. ### AFC (`afc`) - `afc`: STRING. Monto deducido por cuentas de ahorro para el fomento de la construcción (AFC). ### Cooperativa (`cooperative`) - `cooperative`: STRING. Monto deducido por aportes a cooperativas. ### Embargo Fiscal (`tax_embargo`) - `tax_embargo`: STRING. Monto deducido por embargos fiscales. ### Plan Complementario (`complementary_plan`) - `complementary_plan`: STRING. Monto deducido por plan complementario de salud. ### Educación (`education`) - `education`: STRING. Monto deducido por concepto de educación. ### Reembolso (`refund`) - `refund`: STRING. Monto de reembolsos realizados al empleado. ### Deuda (`debt`) - `debt`: STRING. Monto deducido por deudas con la empresa. ## Totales del Documento - `total_earned`: STRING. Total de ingresos ganados por el empleado durante el período. - `deductions_total`: STRING. Total de deducciones aplicadas al pago del empleado. - `total_voucher`: STRING. Monto final después de aplicar ingresos y deducciones. --- # Nómina de eliminación ## JSON de ejemplo ```json title="payroll-delete.json" { "resolution_number": "18760000002", "document_number": "11", "generation_city_id" : "836", "deleting_predecessor": { "number": "SETPNE0017", "cune": "fa6e59a1d6f69b1d4e37d15f7485744ce5f74627496e91fba0058aacb52d580f6482d89faa34c8861a6e29df856f4dfa", "generation_date": "2022-08-28" }, "general_information": { "generation_date": "2022-08-28", "generation_time": "10:00:00" }, "notes": "Documento eliminado por error contable" } ``` ## Diccionario de datos - `resolution_number` (string): Número de resolución. - `document_number` (string): Número de documento. - `generation_city_id` (string): Identificador de la ciudad de generación. - `deleting_predecessor` (object): Predecesor de eliminación. - `number` (string): Número de predecesor. - `cune` (string): CUNE. - `generation_date` (string): Fecha de generación. - `general_information` (object): Información general. - `generation_date` (string): Fecha de generación. - `generation_time` (string): Hora de generación. - `notes` (string): Notas. --- # Guía: Factura Electrónica Simple ## Introducción Esta guía te mostrará cómo emitir tu primera factura electrónica usando la API de MATIAS. Es perfecta si acabas de empezar y tienes un caso de uso simple: vender un producto o servicio a un cliente empresario. ## ¿Cuándo usar esta guía? ✅ Tu primer documento electrónico ✅ Venta sin descuentos ✅ Cliente empresario identificado ✅ Pago único (no a plazo) ✅ No necesitas casos especiales :::tip Si tu caso es más complejo (exportación, múltiples monedas, descuentos), consulta las otras guías. ::: ## Requisitos Previos Antes de empezar, asegúrate de tener: - ✅ Usuario y contraseña en MATIAS API - ✅ Token OAuth2 obtenido - ✅ Resolución de facturación de DIAN - ✅ Certificado digital vigente - ✅ Datos de tu empresa ## Paso 1: Obtener el Token de Autenticación Primero, necesitas autenticarte en la API: ### Solicitud ```bash curl -X POST {{url}}/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "tu-email@empresa.com", "password": "tu-contraseña", "remember_me": 0 }' ``` ### Respuesta ```json { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM...", "user": { "id": 1, "email": "tu-email@empresa.com", "name": "TU EMPRESA S.A.S." }, "expires_at": "2026-05-06 14:30:00", "message": "Bienvenido a Matias. Su sesión ha sido iniciada con éxito.", "success": true } ``` :::info Guarda este `access_token`, lo usarás en todos los próximos pasos. ::: :::tip Alternativa: Personal Access Tokens En v3.0.0 puedes crear **Personal Access Tokens** directamente desde tu cuenta sin hacer login cada vez: ```bash # 1. Primero haz login para obtener un token temporal # 2. Luego crea un PAT que dure más tiempo (1-365 días; 90 por defecto) curl -X POST {{url}}/tokens \ -H "Authorization: Bearer {token_temporal}" \ -H "Content-Type: application/json" \ -d '{ "name": "Token Produccion", "expires_in_days": 60 }' ``` [📖 Ver guía completa de Personal Access Tokens](https://docs.matias-api.com/docs/endpoints/intro-auth#personal-access-tokens) ::: ## Paso 2: Preparar los Datos de la Factura Reúne la información necesaria: ### 📝 Información del Documento ```json { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "2002", "date": "2024-10-17", "time": "14:30:00" } ``` ### 📝 Información del Cliente (Comprador) ```json { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" } ``` :::info **Nota Importante:** El emisor (tu empresa) NO se envía en el JSON. La API identifica al emisor por el **token de autenticación**. Solo debes enviar información del cliente (comprador). ::: ## Paso 3: Crear la Estructura JSON de la Factura Monta el JSON completo usando la estructura real del API: ```json { "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "2002", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "224.00" } ], "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] } ], "legal_monetary_totals": { "line_extension_amount": "100.00", "tax_exclusive_amount": "100.00", "tax_inclusive_amount": "119.00", "payable_amount": 119.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] } ``` :::warning **Puntos Importantes:** - `type_document_id: 7` → Factura de venta - `operation_type_id: 1` → Estándar - `line_extension_amount` = `invoiced_quantity` × `price_amount` - `tax_inclusive_amount` = `tax_exclusive_amount` + suma de `tax_totals.tax_amount` - El `customer.dni` NO incluye dígito verificador en este caso ::: ## Paso 4: Validar la Factura Localmente Antes de enviarla a la API, verifica: ✅ **Identificación** - `resolution_number: "18764074347312"` (válida) - `prefix: "LZT"` (registrada en DIAN) - `document_number: "2002"` (no duplicado) - `type_document_id: 7` (Factura de venta) - `operation_type_id: 1` (Operación estándar) ✅ **Cliente** - `dni: "1063279307"` (válido con dígito verificador) - `company_name: "LOPEZ GOMEZ LEWIS OSWALDO"` (no vacío) - `email: "cliente4@ejemplo.com"` (formato correcto) - `country_id: "45"` (Colombia) ✅ **Líneas** - `invoiced_quantity: "2"` > 0 - `price_amount: "50"` > 0 - `line_extension_amount: "100.00"` = 2 × 50 ✓ - `tax_totals` con cálculo: 100 × 0.19 = 19 ✓ ✅ **Totales** - `tax_exclusive_amount: "100.00"` = suma de líneas - `tax_inclusive_amount: "119.00"` = 100 + 19 ✓ - `payable_amount: 119.00` = `tax_inclusive_amount` ✓ ✅ **Pagos** - `value_paid: "224.00"` = `payable_amount` ✓ - `payment_method_id: 1` (válido) - `means_payment_id: 10` (válido) ## Paso 5: Enviar la Factura a la API Una vez validada localmente, envía la factura: ```bash curl -X POST "{{url}}/invoice" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "resolution_number": "18764074347312", "prefix": "LZT", "notes": "Nota del documento", "document_number": "2002", "type_document_id": 7, "operation_type_id": 1, "graphic_representation": 0, "send_email": 1, "customer": { "country_id": "45", "city_id": "836", "identity_document_id": "1", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "LOPEZ GOMEZ LEWIS OSWALDO", "dni": "1063279307", "mobile": "3108435423", "email": "cliente4@ejemplo.com", "address": "Calle 64 #1823", "postal_code": "661002" }, "lines": [ { "invoiced_quantity": "2", "quantity_units_id": "1093", "line_extension_amount": "100.00", "free_of_charge_indicator": false, "description": "TIJERA NECROPSIA AVES", "code": "HMT83", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "50", "base_quantity": "2", "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ] } ], "legal_monetary_totals": { "line_extension_amount": "100.00", "tax_exclusive_amount": "100.00", "tax_inclusive_amount": "119.00", "payable_amount": 119.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 19, "taxable_amount": 100, "percent": 19 } ], "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "119.00" } ] }' ``` ### Respuesta Exitosa ```json { "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "response": { "ErrorMessage": { "string": [] }, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura electrónica LZT2002, ha sido autorizada.", "XmlBase64Bytes": "", "XmlBytes": { "_attributes": { "nil": "true" } }, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "XmlFileName": "fv09010914030002500000095" }, "XmlBase64Bytes": "", "AttachedDocument": { "pathZip": "1/ad/z09010914030002500000042.zip", "path": "1/ad/ad09010914030002500000041.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000041.xml", "data": "" }, "qr": { "qrDian": "", "url": "", "path": "1/fv09010914030002500000095.png", "data": "" }, "pdf": { "path": "1/fv09010914030002500000095.pdf", "url": "https://sandbox-api.matias-api.com/pdf/1/fv09010914030002500000095.pdf", "data": "" }, "success": true } ``` :::info **Campos Clave en la Respuesta:** - `message`: Mensaje genérico del API - `XmlDocumentKey` (CUFE): Identificador único del documento en DIAN - `response.IsValid`: "true" = documento válido - `response.StatusCode`: "00" = procesado correctamente - `response.StatusMessage`: Confirmación de autorización - `XmlFileName`: Nombre del documento en portal DIAN - `success`: true = operación exitosa - `pdf.url`: Enlace para descargar el PDF - `AttachedDocument.url`: Enlace del XML ::: ### Si Hay Errores Si el número ya fue validado ante la DIAN, la API responde **HTTP 400** con el registro del envío existente en `data` (ver [Respuestas de la API](https://docs.matias-api.com/docs/response-json.md)): ```json { "message": "El documento (Factura electrónica) con numero LZT2002 ya se encuentra validado.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT2002", "XmlDocumentKey": "cf9864294501e8a9578235dd2ab3c4fd...", "is_valid": 1 }, "success": false } ``` :::warning **Códigos de Error Comunes:** - Documento duplicado - Datos de cliente inválidos - Cálculos de totales incorrectos - NIT sin dígito verificador o incorrecto - Resolución no válida o vencida ::: ## Paso 6: Consultar Detalles del Documento Para obtener los detalles completos de tu factura después de haberla creado: ```bash curl -X GET "{{url}}/status?number=LZT2002&type_document_id=7" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" ``` ### Respuesta ```json { "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "response": { "ErrorMessage": { "string": [] }, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura electrónica LZT2002, ha sido autorizada.", "XmlBase64Bytes": "", "XmlBytes": { "_attributes": { "nil": "true" } }, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "XmlFileName": "fv09010914030002500000095" }, "XmlBase64Bytes": "", "AttachedDocument": { "pathZip": "1/ad/z09010914030002500000042.zip", "path": "1/ad/ad09010914030002500000041.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000041.xml", "data": "" }, "qr": { "qrDian": "", "url": "", "path": "1/fv09010914030002500000095.png", "data": "" }, "pdf": { "path": "1/fv09010914030002500000095.pdf", "url": "https://sandbox-api.matias-api.com/pdf/1/fv09010914030002500000095.pdf", "data": "" }, "success": true } ``` :::success **¡Tu factura está lista!** Con los datos de la respuesta puedes: - 📥 **Descargar el PDF**: Usa `pdf.url` - 📄 **Obtener el XML**: Usa `AttachedDocument.url` - 📱 **Compartir el QR**: Usa `qr.url` - ✅ **Verificar**: El CUFE (`XmlDocumentKey`) es único e irrepetible ::: **Tiempo estimado de validación DIAN**: 30 minutos a 2 horas ## Paso 7: Enviar Comprobante al Cliente Si configuraste `send_email: 1`, la API enviará automáticamente. Para enviar nuevamente, usa el CUFE de la factura (`XmlDocumentKey`) como `{trackId}` (ver [envío y reenvío de correos](https://docs.matias-api.com/docs/endpoints/queries-status#correos-adquirentes)): ```bash curl -X POST "{{url}}/documents/sendmail/{trackId}" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email_to": "compras@cliente.com" }' ``` ## Checklist de Éxito - [x] JSON coincide con estructura del API - [x] Todos los campos requeridos presentes - [x] Cálculos de totales e impuestos correctos - [x] NIT del cliente válido - [x] Respuesta HTTP 200 o 201 - [x] `status_id: 1` o superior en respuesta - [x] CUFE generado correctamente - [x] Cliente recibió email ## Troubleshooting ### ❌ "invalid_json" **Causa**: JSON malformado o campos extras **Solución**: Usa la estructura exacta del ejemplo. Revisa la documentación de campos obligatorios. ### ❌ "NIT verificador inválido" **Causa**: El dígito verificador del NIT es incorrecto **Solución**: Usa el validador NITValidator en herramientas interactivas ### ❌ "Duplicate document" **Causa**: Ya existe una factura con el mismo `prefix` y `document_number` **Solución**: Incrementa el `document_number` o usa diferente `prefix` ### ❌ "tax_amount calculation error" **Causa**: Los impuestos no se calculan correctamente **Solución**: `tax_amount` = `taxable_amount` × (`percent` / 100) ## Próximos Pasos ¡Felicidades! Has emitido tu primera factura electrónica. Ahora puedes: - 📖 [Consultar la guía de Facturas con Descuentos](https://docs.matias-api.com/docs/use-cases/invoice-with-discounts.md) - 📖 [Aprender sobre Exportaciones](https://docs.matias-api.com/docs/use-cases/export-scenarios.md) - 📋 [Ver Referencias Técnicas](https://docs.matias-api.com/docs/billing-fields.md) --- **Última actualización**: Octubre 2025 **Tiempo estimado**: 15 minutos **Nivel de dificultad**: ⭐ Principiante --- # 🏷️ Guía: Factura con Descuentos ## 📋 Introducción Esta guía te enseñará cómo emitir una factura electrónica **con descuentos comerciales**. Los descuentos se aplican a nivel de línea y reducen la base imponible para el cálculo de impuestos. --- ## 🎯 ¿Cuándo usar esta guía? ✅ **Cliente mayorista** con descuento ✅ **Descuento por volumen** o promoción ✅ **Descuento comercial** o rebaja ✅ **Operación con incentivo** al cliente :::warning ⚠️ Punto Crítico En el API MATIAS, los descuentos se aplican usando **`allowance_charges`** dentro de cada línea del producto, **NO** como campo separado global. ::: --- ## 📦 Requisitos Previos Antes de empezar, asegúrate de tener: - ✅ Token de autenticación válido ([ver guía](https://docs.matias-api.com/docs/intro#obtener-el-token-de-acceso)) - ✅ Resolución de facturación activa - ✅ Información completa del cliente - ✅ Productos con precio base y porcentaje de descuento --- ## 🔧 Estructura de Descuentos en el API En MATIAS API, los descuentos se manejan así: ```json "allowance_charges": [ { "amount": "VALOR_DESCUENTO", "base_amount": "VALOR_ORIGINAL", "charge_indicator": false, "allowance_charge_reason": "Promocion" } ] ``` ### 📊 Descripción de Campos | Campo | Tipo | Descripción | Ejemplo | |-------|------|-------------|----------| | `amount` | decimal | **Valor del descuento** en dinero | `22.00` | | `base_amount` | decimal | **Valor original** (price_amount × quantity) | `220.00` | | `charge_indicator` | boolean | `false` = descuento, `true` = cargo | `false` | | `allowance_charge_reason` | string | **Motivo del descuento** | `"Promocion"` | --- ## 🧮 La Matemática de Descuentos La fórmula correcta es: **PASO 1:** PRECIO UNITARIO × CANTIDAD = SUBTOTAL ↓ **PASO 2:** SUBTOTAL - DESCUENTO = VALOR FINAL ↓ **PASO 3:** VALOR FINAL × TASA IVA = IMPUESTO ↓ **PASO 4:** VALOR FINAL + IMPUESTO = TOTAL ### 💡 Ejemplo Práctico #### 📊 Datos Iniciales - 💵 Precio unitario: **$220.00** - 📦 Cantidad: **1** - ➕ Subtotal: **$220.00** #### ✅ Resultado Final - 🏷️ Descuento (10%): **$22.00** - 💰 Valor final: **$198.00** - 📋 Impuesto (0%): **$0.00** - 🎯 **TOTAL: $198.00** --- ## 📄 Paso 1: Ejemplo Real del API :::tip JSON Listo para Usar Este es el JSON **real y completo** que acepta MATIAS API. Puedes copiarlo y adaptarlo a tu caso. ::: ```json title="factura-con-descuento.json" { "resolution_number": "18764074347312", "prefix": "FEV", "notes": "Factura con descuento comercial", "document_number": "2005", "graphic_representation": 0, "send_email": 1, "operation_type_id": 1, "type_document_id": 7, "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "198.00" } ], "customer": { "country_id": "45", "city_id": "1041", "identity_document_id": "3", "type_organization_id": 2, "tax_regime_id": 2, "tax_level_id": 5, "company_name": "CONSUMIDOR FINAL", "dni": "222222222222", "mobile": "3043965204", "email": "compras@cliente.com", "address": "CALLE 22 NRO. 32 29", "postal_code": "76834" }, "lines": [ { "invoiced_quantity": "1", "quantity_units_id": "1093", "line_extension_amount": "198.00", "free_of_charge_indicator": false, "description": "CALZADO OSIRIS D3 2001", "code": "A50824", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "220.00", "base_quantity": "1", "allowance_charges": [ { "amount": "22.00", "base_amount": "220.00", "charge_indicator": false, "allowance_charge_reason": "Promocion" } ] } ], "legal_monetary_totals": { "line_extension_amount": "198.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "198.00", "payable_amount": 198.00 } } ``` --- ## 🔍 Paso 2: Entender la Estructura ### 🎯 Campos Clave para Descuentos Analicemos el JSON línea por línea: | Campo | Valor | Explicación | Fórmula | |-------|-------|-------------|----------| | `price_amount` | `220.00` | 💵 Precio SIN descuento | Precio base | | `base_amount` | `220.00` | 📊 Base para descuento | `price_amount × quantity` | | `amount` (descuento) | `22.00` | 🏷️ Valor del descuento | `220.00 × 0.10` (10%) | | `line_extension_amount` | `198.00` | 💰 Precio FINAL | `220.00 - 22.00` | | `charge_indicator` | `false` | ⬇️ Tipo de ajuste | `false` = descuento | :::tip 💡 Regla de Oro **`line_extension_amount`** siempre debe ser igual a: `(price_amount × quantity) - descuentos` ::: --- ## 🏪 Paso 3: Caso Mayorista con Descuento por Volumen :::info Caso Real Cliente mayorista que compra **100 unidades** y recibe **15% de descuento** por volumen. ::: ```json { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "5045", "operation_type_id": 1, "type_document_id": 7, "graphic_representation": 0, "send_email": 1, "customer": { "country_id": "45", "city_id": "76", "identity_document_id": "2", "type_organization_id": 2, "tax_regime_id": 1, "tax_level_id": 5, "company_name": "CLIENTE MAYORISTA", "dni": "8001234567", "email": "compras@mayorista.com", "address": "Calle 100 #50-80", "postal_code": "050001" }, "lines": [ { "invoiced_quantity": "100", "quantity_units_id": "1093", "price_amount": "10000.00", "line_extension_amount": "850000.00", "description": "PRODUCTO A - 100 unidades", "code": "PRD-A", "type_item_identifications_id": "4", "reference_price_id": "1", "base_quantity": "100", "allowance_charges": [ { "amount": "150000.00", "base_amount": "1000000.00", "charge_indicator": false, "allowance_charge_reason": "Descuento volumen 15%" } ], "tax_totals": [ { "tax_id": "1", "tax_amount": 161500.00, "taxable_amount": 850000.00, "percent": 19 } ] } ], "legal_monetary_totals": { "line_extension_amount": "850000.00", "tax_exclusive_amount": "850000.00", "tax_inclusive_amount": "1011500.00", "payable_amount": 1011500.00 }, "tax_totals": [ { "tax_id": "1", "tax_amount": 161500.00, "taxable_amount": 850000.00, "percent": 19 } ], "payments": [ { "payment_method_id": 1, "means_payment_id": 10, "value_paid": "1011500.00" } ] } ``` ### 🧮 Desglose de Cálculos #### 💰 Cálculo Paso a Paso | 💵 Precio unitario: | **$10,000.00** | |---|---| | 📦 Cantidad: | **100** | | ➕ Subtotal: | **$1,000,000.00** | | 🏷️ Descuento (15%): | **- $150,000.00** | | 💰 Valor final: | **$850,000.00** | | 📊 IVA (19%): | **+ $161,500.00** | | 🎯 TOTAL A PAGAR: | **$1,011,500.00** | --- ## 🚀 Paso 4: Enviar a la API :::warning 🔐 Requisito: Token de Autenticación Asegúrate de tener un token válido. Si no tienes uno, [obtén tu token aquí](https://docs.matias-api.com/docs/intro#obtener-el-token-de-acceso). ::: ```bash curl -X POST {{url}}/invoice \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d @factura-descuento.json ``` ### Respuesta Exitosa ```json { "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "response": { "ErrorMessage": { "string": [] }, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura electrónica FEV2005, ha sido autorizada.", "XmlDocumentKey": "d45f3b2ed042ce0e075891591c3b3a7ae3a9c176ca191dab1bd23e5cdd3b48b8c548a088dfcbe20ee7baa2bed2dccd48", "XmlFileName": "fv09010914030002500000096" }, "AttachedDocument": { "path": "1/ad/ad09010914030002500000042.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000042.xml" }, "pdf": { "path": "1/fv09010914030002500000096.pdf", "url": "https://sandbox-api.matias-api.com/pdf/1/fv09010914030002500000096.pdf" }, "success": true } ``` :::tip ✅ Respuesta Exitosa **Indicadores de éxito:** - ✅ `success: true` - ✅ `StatusCode: "00"` - ✅ `IsValid: "true"` - ✅ CUFE generado (`XmlDocumentKey`) - ✅ URLs de PDF y XML disponibles ::: --- ## 📝 Paso 5: Razones de Descuento Válidas :::tip 💬 Descripción del Descuento En el campo **`allowance_charge_reason`** usa una descripción clara del motivo del descuento: ::: 🎉 **Promocion** Descuento promocional 📦 **Volumen** Descuento por cantidad ⚡ **Pronto pago** Pago inmediato 🎁 **Bonificacion** Bonificación especial 💰 **Rebaja** Rebaja comercial --- ## ✅ Validaciones Críticas :::danger ⚠️ Verificar ANTES de Enviar Estas validaciones son **obligatorias** para que la DIAN acepte tu factura: ::: ### 🔐 Checklist de Validación ☐ `allowance_charges[].amount` ≤ `allowance_charges[].base_amount` El descuento no puede ser mayor al precio base ☐ `line_extension_amount` = `price_amount × quantity - descuentos` El valor final debe reflejar el descuento aplicado ☐ `tax_amount` = `taxable_amount × (percent / 100)` El impuesto se calcula sobre el valor CON descuento ☐ `tax_exclusive_amount` = suma de `line_extension_amount` Total sin impuestos = suma de líneas finales ☐ `tax_inclusive_amount` = `tax_exclusive_amount + impuestos` Total con impuestos incluidos ☐ `payable_amount` = `tax_inclusive_amount` Monto a pagar = total con impuestos ☐ `charge_indicator` = `false` para descuentos false = descuento, true = cargo adicional --- ## 🔧 Troubleshooting ### ❌ Error: "allowance_charges amount exceeds base amount" **❌ Causa:** El descuento es mayor al precio original **✅ Solución:** Verifica que `amount` ≤ `base_amount` ### ❌ Error: "Tax calculated on wrong amount" **❌ Causa:** Impuesto calculado sobre el precio SIN descuento **✅ Solución:** Calcula impuesto sobre `line_extension_amount` (después del descuento) ### ❌ Error: "Total mismatch" **❌ Causa:** Los totales no coinciden **✅ Solución:** Usa la [herramienta TotalCalculator](https://docs.matias-api.com/docs/interactive-tools.md) para validar --- ## 🎯 Próximos Pasos [📄 **Factura Simple** Guía básica sin descuentos](https://docs.matias-api.com/docs/use-cases/simple-invoice.md) [🌎 **Exportación** Facturas internacionales](https://docs.matias-api.com/docs/use-cases/export-scenarios.md) [⚠️ **Errores Comunes** Troubleshooting](https://docs.matias-api.com/docs/use-cases/common-errors.md) [🧮 **Calculadora** Herramientas interactivas](https://docs.matias-api.com/docs/interactive-tools.md) --- 📅 **Última actualización:** Febrero 2026 • ⏱️ **Tiempo estimado:** 20 minutos • 🎯 **Nivel:** ⭐⭐ Intermedio --- # 🌎 Guía: Exportaciones e Internacionalización ## 📋 Introducción Esta guía te enseñará cómo emitir facturas de **exportación**. Las facturas de exportación tienen características especiales: cliente en el exterior, moneda extranjera, y están exentas de IVA. --- ## 🎯 ¿Cuándo usar esta guía? ✅ **Venta a cliente** en el exterior ✅ **Transacción en moneda** extranjera (USD, EUR) ✅ **Productos salen** de Colombia ✅ **Cliente sin NIT** colombiano ✅ **Requiere tasa** de cambio especificada :::warning ⚠️ Punto Crítico En API MATIAS, la exportación se marca con: - 📑 **`type_document_id: 8`** (Factura de Exportación) - 🔄 **`operation_type_id: 1`** (NO usar `operation_type_id: 2`) ::: --- ## 📦 Requisitos Previos Antes de empezar, asegúrate de tener: - ✅ Token de autenticación válido ([ver guía](https://docs.matias-api.com/docs/intro#obtener-el-token-de-acceso)) - ✅ Resolución de facturación activa - ✅ Información del cliente extranjero - ✅ Tasa de cambio (TRM) del día - ✅ Documentos de exportación (si aplica) --- ## 🔑 Características Clave de Exportación | Característica | Valor | 📝 Descripción | |---|---|---| | **Tipo de Documento** | `type_document_id: 8` | 🌎 Factura de Exportación | | **Operación** | `operation_type_id: 1` | 🔄 Operación estándar | | **Moneda** | `currency_id: 272` | 💵 Dólares USD (u otra moneda) | | **IVA** | Exento (0%) | 🚫 Sin impuestos | | **Cliente** | País ≠ Colombia | 🌍 Extranjero | | **Tasa de Cambio** | `payment_exchange_rate` | 💱 TRM del día | ## Paso 1: Información del Cliente Exterior El cliente debe ser de otro país: ```json { "country_id": "239", "identity_document_id": "10", "type_organization_id": 1, "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" } ``` ### 🌍 Códigos de País Comunes 🇺🇸 **Estados Unidos:** 239 🇲🇽 **México:** 137 🇧🇷 **Brasil:** 37 🇵🇦 **Panamá:** 155 🇪🇨 **Ecuador:** 70 🇨🇦 **Canadá:** 41 🇪🇸 **España:** 217 ### 📍 Tipos de Documento para Extranjeros | Código | Tipo de Documento | 📝 Uso | |--------|------------------|------| | `10` | **Pasaporte** | Personas naturales extranjeras | | `3` | **Cédula Extranjería** | Extranjeros residentes en Colombia | | `12` | **NIT Extranjero** | Empresas internacionales | --- ## 💱 Paso 2: Configurar Moneda y Tasa de Cambio :::info TRM - Tasa Representativa del Mercado Usa la **TRM oficial** del día de la transacción. Puedes consultarla en el [Banco de la República](https://www.banrep.gov.co/es/estadisticas/trm). ::: ```json { "currency_id": 272, "payment_exchange_rate": { "exchange_rate": "4243.80", "base_rate": "4243.80", "rate_date": "2025-05-05", "currency_id": 188 } } ``` ### 📊 Descripción de Campos | Campo | Tipo | Descripción | Ejemplo | |-------|------|-------------|----------| | `currency_id` | integer | 💵 Moneda del documento | `272` (USD) | | `exchange_rate` | decimal | 💱 TRM del día (pesos/dólar) | `"4243.80"` | | `base_rate` | decimal | 📊 Tasa base (igual a exchange_rate) | `"4243.80"` | | `rate_date` | date | 📅 Fecha de la transacción | `"2025-05-05"` | | `currency_id` (dentro) | integer | 💴 Moneda base (COP = 188) | `188` | ### 💵 Códigos de Moneda Comunes 💵 **USD (Dólar):** 272 💶 **EUR (Euro):** 273 💴 **COP (Peso):** 170 💵 **MXN (Peso MX):** 274 💶 **BRL (Real):** 275 --- ## 📄 Paso 3: Ejemplo Real de Exportación :::tip JSON Listo para Usar Este es el JSON **real y completo** para una factura de exportación a Estados Unidos. ::: ```json title="factura-exportacion.json" { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "2055", "operation_type_id": 1, "type_document_id": 8, "currency_id": 272, "graphic_representation": 0, "send_email": 1, "payments": [ { "payment_method_id": 1, "means_payment_id": 42, "value_paid": "4243.80" } ], "payment_exchange_rate": { "exchange_rate": "4243.80", "base_rate": "4243.80", "rate_date": "2025-05-05", "currency_id": 188 }, "customer": { "country_id": "239", "identity_document_id": "10", "type_organization_id": 1, "company_name": "AMAZON", "dni": "444444055", "address": "NORTE DE VIRGINIA H10", "city_name": "VIRGINIA", "postal_code": "110121" }, "lines": [ { "invoiced_quantity": "1.00", "quantity_units_id": "1093", "line_extension_amount": "4243.80", "free_of_charge_indicator": false, "description": "HONORARIOS REPRESENTACION LEGAL", "code": "999-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "4243.80", "base_quantity": 1.00 } ], "legal_monetary_totals": { "line_extension_amount": "4243.80", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "4243.80", "payable_amount": 4243.80 } } ``` ### 🧮 Desglose de Cálculos #### 💰 Cálculo en USD | 💵 Precio unitario (USD): | **$4,243.80** | |---|---| | 📦 Cantidad: | **1** | | ➕ Subtotal (USD): | **$4,243.80** | | 🚫 Impuesto: | **$0.00 (Exento)** | | 🎯 TOTAL (USD): | **$4,243.80** | #### 💱 Conversión a COP **TRM:** 4,243.80 COP/USD **Equivalente en COP:** $4,243.80 USD × 4,243.80 = ~$18,000,000 COP --- ## 📦 Paso 4: Exportación con Múltiples Líneas :::info Caso Real Exportación de **textiles a México** con múltiples productos. ::: ```json { "resolution_number": "18764074347312", "prefix": "EXP", "document_number": "2100", "operation_type_id": 1, "type_document_id": 8, "currency_id": 272, "graphic_representation": 0, "send_email": 1, "payments": [ { "payment_method_id": 1, "means_payment_id": 42, "value_paid": "12500.00" } ], "payment_exchange_rate": { "exchange_rate": "4243.80", "base_rate": "4243.80", "rate_date": "2025-05-05", "currency_id": 188 }, "customer": { "country_id": "137", "city_id": "100001", "identity_document_id": "10", "type_organization_id": 2, "company_name": "GRUPO COMERCIAL MEXICO", "dni": "AAA123456789", "mobile": "+52-555-1234567", "email": "compras@grupocomercial.com.mx", "address": "Avenida Paseo de la Reforma 505", "postal_code": "06500" }, "lines": [ { "invoiced_quantity": "100", "quantity_units_id": "1093", "line_extension_amount": "7500.00", "free_of_charge_indicator": false, "description": "TEXTILES - Tela de algodón 100%", "code": "TEX-001", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "75.00", "base_quantity": "100" }, { "invoiced_quantity": "50", "quantity_units_id": "1093", "line_extension_amount": "5000.00", "free_of_charge_indicator": false, "description": "TEXTILES - Hilo de poliéster", "code": "TEX-002", "type_item_identifications_id": "4", "reference_price_id": "1", "price_amount": "100.00", "base_quantity": "50" } ], "legal_monetary_totals": { "line_extension_amount": "12500.00", "tax_exclusive_amount": "0.00", "tax_inclusive_amount": "12500.00", "payable_amount": 12500.00 } } ``` --- ## 🚀 Paso 5: Enviar a la API :::warning 🔐 Requisito: Token de Autenticación Asegúrate de tener un token válido. Si no tienes uno, [obtén tu token aquí](https://docs.matias-api.com/docs/intro#obtener-el-token-de-acceso). ::: ```bash curl -X POST {{url}}/invoice \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d @factura-exportacion.json ``` ### Respuesta Exitosa ```json { "message": "El documento ha sido procesado por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "c56g4c3fe153df1f186902602d4c4b8bf4b0d287db202ebc2ce34f6dee4c59c9d659b199ecgdf31ff8cbb3cfe3eddde59", "response": { "ErrorMessage": { "string": [] }, "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "StatusMessage": "La Factura electrónica LZT2055, ha sido autorizada.", "XmlDocumentKey": "c56g4c3fe153df1f186902602d4c4b8bf4b0d287db202ebc2ce34f6dee4c59c9d659b199ecgdf31ff8cbb3cfe3eddde59", "XmlFileName": "fv09010914030002500000097" }, "AttachedDocument": { "path": "1/ad/ad09010914030002500000043.xml", "url": "https://sandbox-api.matias-api.com/attachments/1/ad/ad09010914030002500000043.xml" }, "pdf": { "path": "1/fv09010914030002500000097.pdf", "url": "https://sandbox-api.matias-api.com/pdf/1/fv09010914030002500000097.pdf" }, "success": true } ``` :::tip ✅ Respuesta Exitosa **Indicadores de éxito:** - ✅ `success: true` - ✅ `StatusCode: "00"` - ✅ `IsValid: "true"` - ✅ CUFE generado (`XmlDocumentKey`) - ✅ URLs de PDF y XML disponibles - ✅ Documento registrado en DIAN ::: --- ## 📃 Documentación Requerida para DIAN ✅ **Certificado de origen** (si aplica según destino) ✅ **Documentos de aduanas** Declaración de exportación ✅ **Declaración de exportación** Formulario DIAN ✅ **Comprobante de cambio** TRM aplicada ✅ **Comprobante de envío** Guía internacional --- ## ✅ Validaciones Críticas :::danger ⚠️ Verificar ANTES de Enviar Estas validaciones son **obligatorias** para facturas de exportación: ::: ### 🔐 Checklist de Validación ☐ `country_id` ≠ 45 (No es Colombia) El cliente debe ser de un país diferente a Colombia ☐ `identity_document_id` es de extranjero (10, 3, 12) Usar pasaporte, cédula extranjería o NIT extranjero ☐ `type_document_id: 8` (Exportación) Tipo de documento específico para exportación ☐ `currency_id: 272` (USD) u otra moneda válida Especificar moneda extranjera correcta ☐ `payment_exchange_rate` completado TRM del día con todos los campos requeridos ☐ IVA = 0% (exento) Las exportaciones no pagan IVA ☐ Totales = suma de líneas Verificar cálculos de totales ☐ `payable_amount` = `tax_inclusive_amount` Monto a pagar coincide con total --- ## 🔧 Troubleshooting ### ❌ Error: "Invalid country for export" **❌ Causa:** `country_id` es 45 (Colombia) **✅ Solución:** Especifica un `country_id` válido del exterior (ej: 239 para USA) ### ❌ Error: "Currency mismatch" **❌ Causa:** Moneda no coincide entre línea y totales **✅ Solución:** Usa `currency_id: 272` (USD) en **todo el documento** ### ❌ Error: "Exchange rate required" **❌ Causa:** `payment_exchange_rate` no está definido **✅ Solución:** Añade `payment_exchange_rate` con TRM del día ### ❌ Error: "Tax not allowed for exports" **❌ Causa:** Se incluyó IVA en una exportación **✅ Solución:** Las exportaciones **NO pagan IVA**. Elimina `tax_totals` o ponlo vacío: `[]` --- ## 🎯 Próximos Pasos [📄 **Factura Simple** Guía básica nacional](https://docs.matias-api.com/docs/use-cases/simple-invoice.md) [🏷️ **Factura con Descuentos** Descuentos comerciales](https://docs.matias-api.com/docs/use-cases/invoice-with-discounts.md) [⚠️ **Errores Comunes** Troubleshooting](https://docs.matias-api.com/docs/use-cases/common-errors.md) [📋 **Campos del Documento** Diccionario técnico](https://docs.matias-api.com/docs/billing-fields.md) --- 📅 **Última actualización:** Febrero 2026 • ⏱️ **Tiempo estimado:** 25 minutos • 🎯 **Nivel:** ⭐⭐⭐ Avanzado --- # ⚠️ Guía: Errores Comunes y Soluciones ## 📋 Introducción Esta guía enumera los errores más frecuentes al trabajar con la API MATIAS de facturación electrónica y cómo solucionarlos. Los ejemplos están basados en estructuras reales del API. 🔐 **Autenticación** Tokens, credenciales, permisos 🧮 **Cálculos** Totales, impuestos, descuentos 👥 **Clientes** NIT, datos obligatorios 📝 **Estructura JSON** Validación, campos requeridos --- ## 🔐 Errores de Autenticación ### ❌ "Unauthorized" / "invalid_token" **⚠️ Error Común:** Este es uno de los errores más frecuentes. Generalmente el token ha expirado. **Respuesta del API:** ```json { "success": false, "message": "Unauthorized - Token inválido o expirado" } ``` **HTTP Status**: 401 - Unauthorized **Causas posibles:** 1. Token OAuth2 ha expirado (duración: 90 días máximo) 2. Token no incluido en header Authorization 3. Token no emitido: si el login falló, `POST /auth/login` responde **404** `"Credenciales inválidas."` y no entrega token 4. Token malformado o corrupto 5. Token revocado manualmente **Solución:** ```bash # 1. Obtener nuevo token curl -X POST "{{url}}/auth/login" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "email": "tu-email@empresa.com", "password": "tu-contraseña", "remember_me": 0 }' # 2. Respuesta con nuevo token { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM...", "user": { "id": 1, "email": "tu-email@empresa.com" }, "expires_at": "2026-05-06 14:30:00", "success": true } # 3. Usar en requests posteriores curl -X POST "{{url}}/invoice" \ -H "Authorization: Bearer YOUR_NEW_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{...}' ``` :::tip 💡 Pro tip **Novedad v3.0.0:** Puedes crear **Personal Access Tokens** con duración configurable (de 1 a 365 días; 90 por defecto) para evitar estar renovando constantemente. [Ver guía de PAT](https://docs.matias-api.com/docs/endpoints/intro-auth#personal-access-tokens) ::: --- ## 📝 Errores de Estructura JSON ### ❌ "Invalid JSON" / "Malformed request" **Respuesta del API:** ```json { "success": false, "message": "Invalid or malformed JSON" } ``` **HTTP Status**: 400 - Bad Request **Causa común - JSON inválido:** Ejemplo de JSON malformado (falta cierre de llave): ```json { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "2002" } ``` **Solución:** ```json { "resolution_number": "18764074347312", "prefix": "LZT", "document_number": "2002" } ``` ### ❌ "Campos requeridos faltantes" **Respuesta del API:** ```json { "success": false, "message": "Required field missing: lines" } ``` **HTTP Status**: 400 - Bad Request **📋 Campos obligatorios:** | Campo | Tipo | Descripción | Ejemplo | |-------|------|-------------|--------| | `resolution_number` | string | 📜 Resolución DIAN de facturación | `"18764074347312"` | | `prefix` | string | 🏷️ Prefijo registrado | `"FEV"`, `"FVD"` | | `document_number` | string | 🔢 Número secuencial | `"2002"` | | `type_document_id` | int | 📄 Tipo de documento | `7` (Factura) | | `operation_type_id` | int | ⚙️ Tipo de operación | `1` (Estándar) | | `customer` | object | 👤 Datos del cliente/comprador | `{...}` | | `lines` | array | 📦 Ítems facturados | `[{...}]` | | `legal_monetary_totals` | object | 💰 Totales finales | `{...}` | | `payments` | array | 💳 Información de pago | `[{...}]` | :::danger ⚠️ Importante Verifica que el JSON incluya **todos estos campos** con valores válidos antes de enviar la solicitud. ::: --- ## 🆔 Errores de Validación de NIT ### ❌ "NIT con dígito verificador inválido" **Respuesta del API:** ```json { "success": false, "message": "El documento (cliente.dni) con numero 8001234567-8, no es valido" } ``` **HTTP Status**: 422 - Unprocessable Entity **Ejemplo:** NIT `8001234567-8` es inválido **🧮 Algoritmo Colombiano Correcto:** **Posición:** 1 2 3 4 5 6 7 8 9 **Dígito:** 8 0 0 1 2 3 4 5 6 **Peso:** 3 7 13 17 19 23 29 37 41 8×3 + 0×7 + 0×13 + 1×17 + 2×19 + 3×23 + 4×29 + 5×37 + 6×41 = 24 + 0 + 0 + 17 + 38 + 69 + 116 + 185 + 246 = **695** 695 ÷ 11 = 63 resto **2** Dígito verificador = 11 - 2 = **9** ✓ **NIT correcto: 800123456-9** **💡 Solución:** 🔍 **NITValidator** [Herramientas Interactivas](https://docs.matias-api.com/docs/interactive-tools.md) 🧮 **Cálculo Manual** Usa el algoritmo anterior --- ## 🧮 Errores de Cálculo de Totales ### ❌ "Total no coincide" **❌ Error Crítico:** Uno de los errores más comunes al crear facturas. Revisa los cálculos cuidadosamente. **Respuesta del API:** ```json { "error": true, "errors": [ { "field": "legal_monetary_totals", "message": "Total no coincide con suma de líneas" } ] } ``` **Ejemplo incorrecto:** ```json { "lines": [ { "invoiced_quantity": "2", "price_amount": "100.00", "line_extension_amount": "200.00", "tax_totals": [ { "tax_id": "1", "tax_amount": 38.00, "taxable_amount": 200.00, "percent": 19 } ] } ], "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "237.00", "payable_amount": 237.00 } } ``` **Respuesta del API:** ```json { "success": false, "message": "La validación de tax_inclusive_amount es incorrecta. Se esperaba 238.00, se recibió 237.00" } ``` **Corrección:** ```json { "legal_monetary_totals": { "line_extension_amount": "200.00", "tax_exclusive_amount": "200.00", "tax_inclusive_amount": "238.00", "payable_amount": 238.00 } } ``` **🎯 Fórmula Correcta:** **tax_inclusive_amount** = tax_exclusive_amount + SUMA(tax_totals[*].tax_amount) **payable_amount** = tax_inclusive_amount ### ❌ "Impuesto calculado incorrectamente" **Respuesta del API:** ```json { "error": true, "errors": [ { "field": "lines[0].tax_totals", "message": "tax_amount no coincide con cálculo esperado" } ] } ``` **Ejemplo incorrecto:** ```json { "lines": [ { "line_extension_amount": "100.00", "tax_totals": [ { "tax_id": "1", "tax_amount": 20.00, // ❌ INCORRECTO: 100 × 0.19 = 19 "taxable_amount": 100.00, "percent": 19 } ] } ] } ``` **Corrección:** ```json { "tax_totals": [ { "tax_id": "1", "tax_amount": 19.00, "taxable_amount": 100.00, "percent": 19 } ] } ``` :::info 📊 Cálculo Correcto 100 × 0.19 = **19.00** ::: **🎯 Fórmula:** **tax_amount** = taxable_amount × (percent / 100) ### ❌ "Descuento excede el monto" **Respuesta del API:** ```json { "success": false, "message": "Allowance/Charge amount cannot exceed line base amount" } ``` **HTTP Status**: 422 - Unprocessable Entity **Ejemplo incorrecto:** ```json { "lines": [ { "price_amount": "100.00", "allowance_charges": [ { "amount": "150.00", "base_amount": "100.00", "charge_indicator": false } ] } ] } ``` **Corrección:** ```json { "allowance_charges": [ { "amount": "50.00", "base_amount": "100.00", "charge_indicator": false } ] } ``` :::tip ✅ Validación El monto debe ser ≤ al monto base **(50 ≤ 100)** ✓ ::: --- ## 👥 Errores de Validación de Cliente ### ❌ "Cliente no válido" / "Invalid party" **Respuesta del API:** ```json { "error": true, "errors": [ { "field": "customer", "message": "Datos de cliente incompletos" } ] } ``` **📋 Campos requeridos del cliente:** | Campo | Obligatorio | Ejemplo | Descripción | |-------|------------|---------|-------------| | `country_id` | ✅ Sí | `"45"` | 🌎 País (45 = Colombia) | | `city_id` | ✅ Sí | `"76"` | 🏙️ Ciudad (76 = Cali) | | `identity_document_id` | ✅ Sí | `"2"` | 🆔 Tipo documento (2 = NIT) | | `type_organization_id` | ✅ Sí | `2` | 🏢 Tipo organización | | `tax_regime_id` | ✅ Sí | `1` | 📊 Régimen tributario | | `company_name` | ✅ Sí | `"CLIENTE LTDA"` | 🏷️ Razón social | | `dni` | ✅ Sí | `"8001234567"` | 🔢 NIT sin dígito | | `email` | ⚪ No | `"cliente@example.com"` | 📧 Email contacto | | `mobile` | ⚪ No | `"3001234567"` | 📱 Teléfono móvil | | `address` | ⚪ No | `"Calle 15 #8-30"` | 📍 Dirección física | **Ejemplo correcto:** ```json { "country_id": "45", "city_id": "76", "identity_document_id": "2", "type_organization_id": 2, "tax_regime_id": 1, "tax_level_id": 5, "company_name": "CLIENTE EMPRESA LTDA", "dni": "8001234567", "email": "compras@cliente.com", "address": "Calle 15 #8-30", "postal_code": "050001" } ``` --- ## 🔄 Errores de Factura Duplicada ### ❌ "Documento duplicado" **⚠️ Atención:** Intentas crear un documento con un número que ya existe en el sistema. **Respuesta del API:** ```json { "message": "El documento (Factura electrónica) con numero LZT2002 ya se encuentra validado.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT2002", "XmlDocumentKey": "cf9864294501e8a9578235dd2ab3c4fd...", "is_valid": 1 }, "success": false } ``` **HTTP Status**: 400 - Bad Request **Causa:** El mismo `prefix` + `document_number` ya fue validado por la DIAN. En `data` llega el registro del envío existente. **💡 Soluciones:** **✅ Opción 1: Incrementar número secuencial** `Actual: LZT-2002 ❌ Ya existe` `Nuevo: LZT-2003 ✓ Use este` **✅ Opción 2: Usar diferente prefijo** `Actual: LZT-2002 ❌ Ya existe` `Nuevo: LZX-2002 ✓ Prefijo diferente` **Solución - Opción 3: Consultar documento anterior** ```bash # Estado del documento (validación y QR) curl -X GET "{{url}}/status?prefix=LZT&number=LZT2002" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" # O búsqueda en el listado de documentos curl -X GET "{{url}}/documents?prefix=LZT&number=LZT2002" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Accept: application/json" ``` :::warning 📜 IMPORTANTE Los prefijos deben estar **registrados previamente** en tu resolución de facturación ante la DIAN. ::: --- ## 🚦 Errores de Transmisión Concurrente, Validación Pendiente y Posible Envío :::danger Regla de oro Ninguno de estos casos se resuelve emitiendo el documento con **otro número**. Consulta primero el estado del documento con `GET /documents/{uuid}` o `GET /status?prefix=...&number=...`. ::: ### ❌ `DOCUMENT_IN_TRANSMISSION` — el número ya se está transmitiendo **HTTP Status**: 409 - Conflict ```json { "code": "DOCUMENT_IN_TRANSMISSION", "message": "El documento LZT2002 ya se está transmitiendo a la DIAN en otra solicitud; consulte su estado antes de enviarlo de nuevo.", "data": { "document_number": "LZT2002" }, "success": false } ``` **Causa:** otra solicitud (un reintento, un doble clic o un lote) está transmitiendo ese mismo número a la DIAN. **Solución:** no envíes el mismo documento en paralelo. Espera unos segundos y consulta su estado antes de reenviarlo. ### ❌ `DOCUMENT_IN_VALIDATION` — la DIAN aún valida el envío anterior **HTTP Status**: 409 - Conflict (solo en producción) ```json { "code": "DOCUMENT_IN_VALIDATION", "message": "La DIAN aún está validando el documento LZT2002 (CUFE/CUDE cf9864294501e8a9...). No se transmitió de nuevo: consulte su estado en unos minutos antes de reenviarlo.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT2002", "document_key": "cf9864294501e8a9..." }, "success": false } ``` **Causa:** el envío anterior de ese número quedó en `StatusCode 98` y la DIAN sigue validándolo. **Solución:** espera unos minutos y consulta el estado con `POST /status/document/{trackId}` usando `data.document_key`. ### ❌ `DIAN_STATUS_UNAVAILABLE` — no se pudo confirmar el envío anterior **HTTP Status**: 503 - Service Unavailable (solo en producción) ```json { "success": false, "code": "DIAN_STATUS_UNAVAILABLE", "message": "No se pudo confirmar en la DIAN el estado del envío anterior del documento LZT2002 (CUFE/CUDE cf9864294501e8a9...). No se transmitió de nuevo para no duplicarlo: intente en unos minutos.", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT2002", "document_key": "cf9864294501e8a9..." } } ``` **Causa:** no es una caída general de la DIAN: la API no pudo confirmar el estado del envío anterior de ese número y, para no duplicarlo, **no transmitió** el documento. **Solución:** reintenta en unos minutos con el mismo número. ### ❌ `DOCUMENT_POSSIBLY_SENT` — posible envío sin respuesta definitiva **HTTP Status**: normalmente 500 - Internal Server Error ```json { "success": false, "message": "No se obtuvo una respuesta definitiva para el documento LZT2002 y es posible que la DIAN lo haya recibido. No lo emita de nuevo: consulte su estado con el uuid del envío antes de cualquier reintento. Detalle: Error interno del servidor. ...", "reference": "3b0c6f5e-8d2a-4c7e-9f1b-2a6d5e4c3b21", "code": "DOCUMENT_POSSIBLY_SENT", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "LZT2002" } } ``` **Causa:** el error (timeout, respuesta inesperada o fallo al guardar) ocurrió **después** de transmitir el documento a la DIAN. El consecutivo se conserva. **Solución:** no reemitas. Consulta `GET /documents/{uuid}` con `data.uuid` (o `GET /status`) y actúa según su estado. Si reportas el caso a soporte, incluye `reference`. ### ❌ "El uuid proporcionado ya se usó en otro documento de la empresa" **HTTP Status**: 409 - Conflict ```json { "success": false, "message": "El uuid proporcionado ya se usó en otro documento de la empresa. No se emitió de nuevo: consulte ese documento o envíe un uuid nuevo." } ``` **Causa:** el `uuid` anti-duplicados que enviaste ya pertenece a otro documento de la empresa. No se transmitió nada. **Solución:** consulta ese documento con `GET /documents/{uuid}` o envía un `uuid` nuevo si se trata de otro documento. ### ⚠️ Avisos de regla 90: `ALREADY_AUTHORIZED_*` / `ALREADY_PROCESSED_NOT_CONFIRMED` En producción, cuando la DIAN responde "documento procesado anteriormente" (regla 90), la API concilia el número con la DIAN y lo informa en `warnings`: | `code` | Significado | Acción | |---|---|---| | `ALREADY_AUTHORIZED_RECOVERED` | La DIAN ya había autorizado ese número con el mismo contenido: se devuelve el documento autorizado. | Ninguna: el documento es válido. | | `ALREADY_AUTHORIZED_OTHER_CONTENT` | La DIAN autorizó ese número con **otro contenido**. El documento enviado no se marcó como válido. | No lo reenvíes con ese número; consulta el documento autorizado. | | `ALREADY_PROCESSED_NOT_CONFIRMED` | La DIAN indicó que lo procesó, pero no confirmó la autorización. | Consulta su estado antes de reenviarlo. | ### ⚠️ Avisos posteriores a la aceptación: `PDF_NOT_GENERATED` / `EMAIL_NOT_SENT` / `ATTACHED_DOCUMENT_NOT_GENERATED` ```json { "warnings": [ { "code": "PDF_NOT_GENERATED", "message": "El documento fue validado por la DIAN, pero no se pudo generar la representación gráfica. No reenvíe el documento: ya es válido ante la DIAN." } ] } ``` **Causa:** la DIAN validó el documento, pero falló un paso posterior (PDF, correo o AttachedDocument). **Solución:** **no reenvíes el documento**: ya es válido. Genera el PDF con `POST /documents/pdf/{trackId}?regenerate=1` o reenvía el correo con `POST /documents/sendmail/{trackId}`. Ver [Avisos de la respuesta](https://docs.matias-api.com/docs/response-json#warnings). ### 📦 Envío masivo: `DUPLICATE_DOCUMENT_UUID` y `POSSIBLY_SENT` - **`DUPLICATE_DOCUMENT_UUID`**: el item termina en `FAILED` porque la empresa ya emitió un documento con ese `uuid`. No se emite de nuevo; consulta el documento existente con `GET /documents/{uuid}`. - **`POSSIBLY_SENT`**: el item no obtuvo una respuesta definitiva de la DIAN después de transmitirse. No lo reenvíes en otro lote; consulta su estado con `GET /documents/{uuid}` usando el `document_uuid` del item. Más detalles en [Envío Masivo de Documentos](https://docs.matias-api.com/docs/bulk-documents.md). --- ## 💱 Errores de Moneda y Exportación ### ❌ "Moneda inválida para exportación" **Respuesta del API:** ```json { "error": true, "errors": [ { "field": "currency_id", "message": "No puedes usar COP en una exportación" } ] } ``` **❌ Incorrecto:** ```json { "type_document_id": 8, // Exportación "currency_id": 170, // COP ❌ "customer": { "country_id": "239" } // USA } ``` **✅ Correcto:** ```json { "type_document_id": 8, // Exportación "currency_id": 272, // USD ✓ "customer": { "country_id": "239" } // USA } ``` ### ❌ "IVA no permitido en exportación" **Respuesta del API:** ```json { "error": true, "errors": [ { "field": "lines[0].tax_totals", "message": "Las exportaciones no pueden tener IVA" } ] } ``` **Incorrecto:** ```json { "tax_totals": [ { "tax_id": "1", "tax_amount": 19.00, "percent": 19 } ] } ``` ❌ **INCORRECTO**: Los documentos de exportación no pueden tener impuestos **Correcto:** ```json { "tax_totals": [] } ``` ✓ **CORRECTO**: Array vacío para exportaciones --- ## 🎯 Matriz de Solución Rápida ### ⚡ Referencia Rápida de Errores Encuentra rápidamente la solución al error que estás experimentando: | 🔍 Síntoma | 🐛 Causa Probable | ✅ Solución | |---------|---|---| | `401 Unauthorized` | 🔐 Token expirado | 🔄 Obtén nuevo token | | `invalid_json` | 📝 JSON malformado | ✔️ Valida sintaxis JSON | | `NIT verificador inválido` | 🔢 Dígito incorrecto | 🔍 Usa herramienta NITValidator | | `Total no coincide` | 🧮 Cálculo de suma | 📊 Valida con TotalCalculator | | `Tax not calculated correctly` | 💰 Fórmula impuesto | 🎯 Usa: amount = base × (percent/100) | | `400 ... ya se encuentra validado.` | 🔄 Número ya validado | 🔍 Consulta `GET /status`; ➕ usa el siguiente número | | `409 DOCUMENT_IN_TRANSMISSION` | 🚦 Envío en paralelo del mismo número | ⏳ Espera y consulta el estado; no reenvíes en paralelo | | `409 DOCUMENT_IN_VALIDATION` | ⏳ La DIAN sigue en `98` | 🔍 Consulta el estado en unos minutos con `data.document_key` | | `503 DIAN_STATUS_UNAVAILABLE` | 🔌 No se confirmó el envío anterior (no se transmitió) | 🔄 Reintenta más tarde con el mismo número | | `DOCUMENT_POSSIBLY_SENT` | 📡 Fallo después de transmitir | 🚫 No reemitas; consulta `GET /documents/{uuid}` | | `409 uuid ya se usó` | 🆔 `uuid` anti-duplicados repetido | 🔍 Consulta ese documento o usa un `uuid` nuevo | | `warnings` `ALREADY_*` | 🔁 Regla 90 conciliada | 📖 Revisa el `code` del aviso | | `warnings` `PDF_NOT_GENERATED` / `EMAIL_NOT_SENT` | 📄 Falló un paso posterior | ✅ Documento válido: no lo reenvíes | | `Currency invalid for export` | 💱 Moneda incorrecta | 💵 Usa currency_id: 272 (USD) | | `Tax not allowed in export` | 🌎 IVA en exportación | ❌ Usa tax_totals: [] | | `Discount exceeds amount` | 🏷️ Descuento > base | ⚖️ Asegura que amount ≤ base_amount | | `Required field missing` | 📋 Campo faltante | 📖 Consulta documentación | --- ## 🛠️ Herramientas Disponibles ### 💡 Prevención de Errores Para evitar errores, usa nuestras herramientas interactivas: 🔍 **NITValidator** Valida y calcula dígito verificador ✅ **JSONValidator** Valida estructura completa 🧮 **TotalCalculator** Calcula totales automáticamente [📱 Acceder a Herramientas →](https://docs.matias-api.com/docs/interactive-tools.md) --- ## 🔬 Debugging **🎯 Guía de Depuración Paso a Paso** Sigue estos pasos para identificar y resolver problemas: ### 📡 Paso 1: Verificar Respuesta Completa ```bash curl -X POST "{{url}}/invoice" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d @factura.json \ -v # Modo verbose para ver toda la respuesta ``` ### ✔️ Paso 2: Validar Localmente ```bash # Instalar jq para parsear JSON npm install -g jq # Validar que el JSON sea válido cat factura.json | jq empty ``` ### 📚 Paso 3: Comparar con Ejemplo Real [📄 **Factura Simple** Ejemplo básico](https://docs.matias-api.com/docs/jsons-billing/invoice.md) [🏷️ **Con Descuento** Ejemplo con descuentos](https://docs.matias-api.com/docs/jsons-billing/invoice-with-discount.md) [🌎 **Exportación** Ejemplo internacional](https://docs.matias-api.com/docs/jsons-billing/USD-invoice-exportation.md) --- ## 🎯 Próximos Pasos [📄 **Factura Simple** Guía básica paso a paso](https://docs.matias-api.com/docs/use-cases/simple-invoice.md) [🏷️ **Factura con Descuentos** Descuentos comerciales](https://docs.matias-api.com/docs/use-cases/invoice-with-discounts.md) [🌎 **Exportaciones** Facturas internacionales](https://docs.matias-api.com/docs/use-cases/export-scenarios.md) [🛠️ **Herramientas** Validadores y calculadoras](https://docs.matias-api.com/docs/interactive-tools.md) --- 📅 **Última actualización:** Febrero 2026 • ⏱️ **Tiempo estimado:** 30 minutos • 🎯 **Nivel:** ⭐⭐ Intermedio --- # ⚡ Quickstart — MATIAS API Sandbox Guía rápida para integrar con el sandbox de MATIAS API en 5 minutos. El sandbox es un ambiente aislado donde puedes probar todos nuestros endpoints sin alterar tus datos reales de producción ni realizar transmisiones fiscales ante la DIAN. --- :::info 🌐 Enlaces Oficiales del Sandbox El entorno de pruebas cuenta con los siguientes puntos de acceso oficiales: * **API Sandbox:** **`https://sandbox-api.matias-api.com/api/ubl2.1`** (marcador `{{SANDBOX_URL}}` en tus peticiones). * **Portal Web Sandbox:** **`https://sandbox-auth.matias-api.com/`** (panel administrativo visual del sandbox). * **Variable de Entorno Base:** En tus integraciones utiliza `{{url}}` / `{{SANDBOX_URL}}` de forma dinámica para alternar fácilmente entre Sandbox y Producción. ::: --- ## 1. Crear cuenta en el Sandbox El acceso al sandbox es **completamente gratuito y no requiere contrato**. Para comenzar tus pruebas, debes crear una **cuenta independiente en el entorno Sandbox**, ya sea desde el portal web visual o mediante el endpoint API de registro: **cURL** ```bash curl -X POST {{SANDBOX_URL}}/register \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "first_name": "Tu Nombre", "last_name": "Tu Apellido", "company_name": "Mi Empresa SAS", "email": "tu@email.com", "password": "tu-password-seguro", "password_confirmation": "tu-password-seguro", "dni": "900123456" }' ``` **JavaScript (Axios)** ```javascript import axios from 'axios'; const SANDBOX_URL = 'https://sandbox-api.matias-api.com/api/ubl2.1'; const response = await axios.post(`${SANDBOX_URL}/register`, { first_name: "Tu Nombre", last_name: "Tu Apellido", company_name: "Mi Empresa SAS", email: "tu@email.com", password: "tu-password-seguro", password_confirmation: "tu-password-seguro", dni: "900123456" }, { headers: { Accept: 'application/json' } }); console.log(response.data); ``` **PHP (Guzzle)** ```php use GuzzleHttp\Client; $client = new Client(); $sandboxUrl = 'https://sandbox-api.matias-api.com/api/ubl2.1'; $response = $client->post("{$sandboxUrl}/register", [ 'headers' => ['Accept' => 'application/json'], 'json' => [ 'first_name' => 'Tu Nombre', 'last_name' => 'Tu Apellido', 'company_name' => 'Mi Empresa SAS', 'email' => 'tu@email.com', 'password' => 'tu-password-seguro', 'password_confirmation' => 'tu-password-seguro', 'dni' => '900123456' ] ]); echo $response->getBody(); ``` **Respuesta Exitosa (HTTP 200):** no incluye token ni datos de la cuenta. ```json { "message": "Empresa creada con éxito. Verifique su dirección de correo electrónico: tu@email.com", "success": true } ``` - El `dni` va **solo con dígitos** (sin dígito de verificación, puntos ni guiones); el DV lo calcula la API. - En el sandbox, la cuenta principal recibe una suscripción de **5.000 documentos por 1 año**. - Envía siempre `Accept: application/json`: sin esa cabecera, los errores de validación (422) llegan como una redirección (HTTP 302). Ver todos los campos y errores en [Registro en la API](https://docs.matias-api.com/docs/intro#registro-en-la-api). ### 1.1 Verificar el correo (obligatorio) La cuenta queda con el correo **sin verificar**. Abre el enlace que llega a tu correo antes de iniciar sesión: hasta hacerlo, `POST /auth/login` responde **HTTP 404**: ```json { "message": "El correo electrónico no ha sido verificado. Comuníquese con el administrador.", "success": false } ``` Si el correo no llegó o el enlace venció, pide uno nuevo: ```bash curl -X POST {{SANDBOX_URL}}/email/verification-notification \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"email": "tu@email.com"}' ``` Más detalles en [Verificación de Correo](https://docs.matias-api.com/docs/endpoints/intro-auth#verificacion-correo). :::warning ⚠️ Cuentas Separadas: Producción vs Sandbox **Las credenciales de Producción NO funcionan en Sandbox, ni las de Sandbox en Producción.** Debes crear **DOS CUENTAS INDEPENDIENTES** según el entorno en el que vayas a operar: * **Cuenta de Sandbox:** Creada en `https://sandbox-auth.matias-api.com/` o vía `POST {{SANDBOX_URL}}/register`. No requiere contrato y es 100% gratuita para desarrollo y pruebas. * **Cuenta de Producción:** Creada en el ambiente productivo proporcionado al contratar el servicio. Requiere certificado digital real y resoluciones DIAN vigentes. ::: --- ## 2. Iniciar sesión en el Sandbox Con el correo verificado, inicia sesión con las **credenciales de tu cuenta de Sandbox** para obtener el token JWT de acceso (`access_token`): ```bash curl -X POST {{SANDBOX_URL}}/auth/login \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "email": "tu@email.com", "password": "tu-password-seguro" }' ``` **Respuesta Exitosa (HTTP 200):** ```json { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...", "token_id": "f2e1eb82d52213a6b1c0d3e4f5a60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9", "user": { "id": 1, "type_id": 2, "first_name": "TU NOMBRE", "last_name": "TU APELLIDO", "email": "tu@email.com", "avatar": null, "active": 1, "name": "TU NOMBRE TU APELLIDO", "avatarUrl": null, "user_type": { "id": 2, "user_type_name": "DESARROLLADOR", "type": 2, "active": 1 } }, "expires_at": "2026-05-15 20:00:00", "message": "Bienvenido. Su sesión ha sido iniciada con éxito.", "success": true } ``` Si el login falla, la API responde **HTTP 404** (no 401) con `message` y `success: false` (por ejemplo, `Credenciales inválidas.`). Ver todos los motivos en [Autenticación](https://docs.matias-api.com/docs/endpoints/intro-auth#autenticacion). --- ## 3. Generar un Personal Access Token (PAT) Genera tu token de acceso persistente de larga duración para realizar pruebas automatizadas de integración: ```bash curl -X POST {{SANDBOX_URL}}/tokens \ -H "Authorization: Bearer {access_token}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"name": "Token Integracion ERP Sandbox"}' ``` `expires_in_days` es opcional (de 1 a 365; por defecto, 90). Cada usuario puede crear hasta **10 tokens por día**; al superarlo, la API responde **HTTP 429**. **Respuesta Exitosa (HTTP 201):** ```json { "dataRecords": { "data": { "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...", "token_id": "cb6282ed9f5cb116416928518f9dd93b53aafc2c0273722bbe5eb7eb96d7ccfac10178f43b8fe617", "name": "Token Integracion ERP Sandbox", "description": null, "expires_at": "2026-05-02 12:00:00", "created_at": "2026-02-01 12:00:00" } }, "message": "Token creado exitosamente. Asegúrese de guardar el token, ya que no podrá verlo nuevamente.", "success": true } ``` El token está en `dataRecords.data.token` y solo se muestra **una vez**: guárdalo en ese momento. :::tip 🔑 Autenticación JWT Estándar Tanto en producción como en el ambiente sandbox, el token generado es un **JWT estándar**, garantizando un esquema de autenticación idéntico y seguro para todos tus entornos. ::: --- ## 4. Enviar documentos electrónicos El sandbox soporta **todos los tipos de documento** de la API. A continuación se detallan los comandos estructurados por módulo: ### 4.1 Factura Electrónica y Sector Salud (Resolución 000948 de 2026) ```bash # Factura Electrónica Estándar curl -X POST {{SANDBOX_URL}}/invoice \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @tu-factura.json # Factura Electrónica del Sector Salud (con objeto health) curl -X POST {{SANDBOX_URL}}/invoice \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @factura-salud.json ``` ### 4.2 Notas Crédito y Débito ```bash # Enviar Nota Crédito (Estándar o Sector Salud) curl -X POST {{SANDBOX_URL}}/notes/credit \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @nota-credito.json # Enviar Nota Débito curl -X POST {{SANDBOX_URL}}/notes/debit \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @nota-debito.json ``` ### 4.3 Documento P.O.S Electrónico ```bash # Documento POS curl -X POST {{SANDBOX_URL}}/pos \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @documento-pos.json ``` ### 4.4 Documento Soporte y Nota de Ajuste ```bash # Enviar Documento Soporte Electrónico curl -X POST {{SANDBOX_URL}}/ds/document \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @documento-soporte.json # Enviar Nota de Ajuste a Documento Soporte curl -X POST {{SANDBOX_URL}}/ds/adjustment-note \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @nota-ajuste-ds.json ``` ### 4.5 Nómina Electrónica ```bash # Enviar Nómina Individual curl -X POST {{SANDBOX_URL}}/ep/payroll \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @nomina.json # Enviar Reemplazo de Nómina curl -X POST {{SANDBOX_URL}}/ep/payroll/replace \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @nomina-replace.json # Enviar Eliminación de Nómina curl -X POST {{SANDBOX_URL}}/ep/payroll/delete \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @nomina-delete.json ``` ### 4.6 Documentos con Numeración Automática (Auto-increment) ```bash # Factura con consecutivo automático curl -X POST {{SANDBOX_URL}}/auto-increment/invoices \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d @factura-auto.json # Nota Crédito con consecutivo automático curl -X POST {{SANDBOX_URL}}/auto-increment/credit-notes \ -H "Authorization: Bearer {tu_token}" \ -d @nc-auto.json # Documento Soporte con consecutivo automático curl -X POST {{SANDBOX_URL}}/auto-increment/support-documents \ -H "Authorization: Bearer {tu_token}" \ -d @ds-auto.json # Documento POS con consecutivo automático curl -X POST {{SANDBOX_URL}}/auto-increment/pos-documents \ -H "Authorization: Bearer {tu_token}" \ -d @pos-auto.json ``` :::tip 🔄 Reenvíos de Auto-incremento Todos los endpoints con numeración automática soportan la llamada **`PATCH /{uuid}`** para gestionar reenvíos transparentes de documentos previamente generados. ::: ### 4.7 Eventos RADIAN (Recepción y Trazabilidad) En el sandbox, el módulo de recepción está **siempre habilitado** (no se responde `403` `RECEPTION_NOT_IN_PLAN`) y el cupo de recepción es **ilimitado**. Aun así, el sandbox **no crea** los prerrequisitos de la recepción: sin ellos, importar o enviar eventos responde `400`. :::warning Prerrequisitos antes de importar o enviar eventos 1. **Numeración activa de eventos** (`type_document_id` `12`), registrada en el portal o con [`POST /resolutions`](https://docs.matias-api.com/docs/endpoints/dian-resolutions#configuracion-manual). Sin ella: `400` `No tiene una numeración activa para los eventos de recepción`. 2. **Persona de recepción**, creada con `POST /events/reception-people` (ver [Personas de Recepción](https://docs.matias-api.com/docs/endpoints/events-radian#personas-recepcion)). Sin ella: `400` `No tiene una persona de recepción asignada` (al importar, puedes evitarlo enviando los datos de la persona en `reception_person`). 3. *(Opcional)* **Modo automático**: una empresa nueva arranca en modo **manual**, así que importar un documento no crea eventos. Si quieres que los eventos 030, 032 y 033 se generen solos, activa el modo con `PUT /events/reception-people/mode` y `{"send_events": true}` (ver [modo de eventos](https://docs.matias-api.com/docs/endpoints/events-radian#eventos-automaticos)). ::: ```bash # 1. Registrar la persona de recepción (la primera queda por defecto) curl -X POST {{SANDBOX_URL}}/events/reception-people \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "identity_document_id": 1, "dni": "1234567890", "first_name": "Nombre", "last_name": "Apellido", "job_title": "Contador" }' # 2. (Opcional) Activar el modo automático de eventos curl -X PUT {{SANDBOX_URL}}/events/reception-people/mode \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"send_events": true}' # Importar un documento por CUFE/trackId curl -X POST {{SANDBOX_URL}}/events/import-track-id \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d '{"trackId": "cufe-del-documento"}' # Listar recepciones de documentos curl -X GET {{SANDBOX_URL}}/events/document-receptions \ -H "Authorization: Bearer {tu_token}" # Enviar evento de acuse de recibo (030) curl -X POST {{SANDBOX_URL}}/events/send/{trackId} \ -H "Authorization: Bearer {tu_token}" \ -H "Content-Type: application/json" \ -d '{"code": "030", "notes": "Acuse de recibo"}' # Consultar estado del evento curl -X GET {{SANDBOX_URL}}/events/status/{trackId} \ -H "Authorization: Bearer {tu_token}" ``` --- ## 5. Simulación de Errores y Validaciones El sandbox te permite forzar respuestas y escenarios de error en **cualquier** endpoint de documentos mediante la cabecera HTTP `X-Sandbox-Force-Status`: ```bash # Simular documento rechazado por reglas de validación DIAN curl -X POST {{SANDBOX_URL}}/invoice \ -H "Authorization: Bearer {tu_token}" \ -H "X-Sandbox-Force-Status: ERROR_REJECTED" \ -d @tu-factura.json # Simular timeout o indisponibilidad de la DIAN curl -X POST {{SANDBOX_URL}}/ep/payroll \ -H "Authorization: Bearer {tu_token}" \ -H "X-Sandbox-Force-Status: ERROR_TIMEOUT" \ -d @nomina.json ``` :::warning 🛡️ Aislamiento del Entorno La cabecera `X-Sandbox-Force-Status` **solo es procesada en el sandbox**. En producción, esta cabecera es ignorada por motivos de seguridad e integridad tributaria. ::: Consulta el catálogo completo de cabeceras en la guía de [Magic Values](https://docs.matias-api.com/docs/sandbox/magic-values.md). --- ## 6. Verificación de Cabeceras del Entorno Todas las respuestas emitidas por el sandbox inyectan la siguiente cabecera HTTP de diagnóstico: ```http X-MATIAS-Environment: sandbox ``` --- ## 📊 Matriz de Endpoints Soportados en el Sandbox El sandbox ofrece paridad funcional total con el entorno de producción para los 20 módulos de la API: ### 📄 1. Emisión y Transmisión de Documentos | Endpoint API | Método | Tipo de Documento / Operación | |:---|:---:|:---| | `/invoice` | `POST` | Factura Electrónica estándar y Sector Salud (Resolución 000948 de 2026) | | `/pos` | `POST` | Documento Equivalente POS Electrónico | | `/notes/credit` | `POST` | Nota Crédito electrónica (incluye notas en salud `SS-CUDE`) | | `/notes/debit` | `POST` | Nota Débito electrónica | | `/ds/document` | `POST` | Documento Soporte Electrónico | | `/ds/adjustment-note` | `POST` | Nota de Ajuste a Documento Soporte | | `/ep/payroll` | `POST` | Nómina Electrónica Individual | | `/ep/payroll/replace` | `POST` | Reemplazo de Nómina Electrónica | | `/ep/payroll/delete` | `POST` | Eliminación de Nómina Electrónica | | `/auto-increment/invoices` | `POST` | Factura con consecutivo automático | | `/auto-increment/credit-notes` | `POST` | Nota Crédito automática | | `/auto-increment/debit-notes` | `POST` | Nota Débito automática | | `/auto-increment/support-documents` | `POST` | Documento Soporte automático | | `/auto-increment/adjustment-notes` | `POST` | Nota de Ajuste automática | | `/auto-increment/pos-documents` | `POST` | Documento POS automático | | `/auto-increment/pos-credit-notes` | `POST` | Nota Crédito POS automática | | `/auto-increment/pos-debit-notes` | `POST` | Nota Débito POS automática | | `/bulk/documents` | `POST` | Emisión Masiva de Documentos (Bulk) | ### 📥 2. Eventos RADIAN y Consultas de Estado | Endpoint API | Método | Descripción | |:---|:---:|:---| | `/events/import-track-id` | `POST` | Importar documento por CUFE/trackId | | `/events/document-receptions` | `GET` | Listar recepciones de documentos | | `/events/send/{trackId}` | `POST` | Enviar evento DIAN (030, 031, 032, 033) | | `/events/send-bulk` | `POST` | Enviar en bloque el acuse 030 de varios documentos | | `/events/{id}/cancel` | `POST` | Cancelar evento RADIAN en estado PENDING | | `/events/status/{trackId}` | `GET` | Consultar estado del evento RADIAN | | `/events/mailbox` | `GET` | Bandeja del buzón de recepción (correos entrantes) | | `/events/mailbox/{id}` | `GET` | Detalle de un correo del buzón | | `/events/mailbox/address` | `GET` | Dirección del buzón de recepción de la empresa | | `/events/mailbox/quota` | `GET` | Consumo del cupo de recepción (ilimitado en sandbox) | | `/events/mailbox/{id}/retry` | `POST` | Reintentar el procesamiento de un correo del buzón | | `/events/reception-people` | `GET` · `POST` | Listar o registrar personas de recepción | | `/events/reception-people/mode` | `PUT` | Cambiar el modo de eventos de la empresa (`send_events`) | | `/events/reception-people/{uuid}` | `PUT` · `DELETE` | Actualizar o eliminar una persona de recepción | | `/events/reception-people/{uuid}/default` | `POST` | Marcar una persona de recepción como la de por defecto | | `/status/zip/{trackId}` | `POST` | Consultar estado ZIP de envío | | `/status/document/{trackId}` | `POST` | Consultar estado del documento | | `/documents/{uuid}/files` | `GET` | Obtener archivos asociados (PDF, XML, ApplicationResponse) | ### 🛠️ 3. Configuración, Catálogos y Utilidades | Familia de Endpoints | Descripción del Comportamiento | |:---|:---| | `/auth/*` y `/tokens/*` | Autenticación, gestión de sesiones y Personal Access Tokens (PAT). | | `/profile/*` | Consulta y actualización de datos de usuario. | | `/company/*` | Consulta, actualización de empresa, logos y gestión de clientes. | | `/resolutions/*` | Registro, consulta y sincronización de numeraciones DIAN. | | `/software/*` y `/certificate/*` | Configuración de software DIAN y certificado digital de firma. | | `/currency/*` | Consulta de monedas y configuración de tasas de cambio (TRM). | | `/settings/templates/*` y `/settings/reports/*` | Plantillas PDF y encabezados de reporte. | | `/webhooks/*` | Registro, delivery history y firma HMAC de webhooks. | | `/memberships/*` | Consulta de membresías y consumo de documentos. | | `/numbers-to-letters` y `/digit-verification` | Utilidades públicas de conversión a letras y dígito de verificación. | --- ## ⚖️ Comparativa: Producción vs Sandbox | Aspecto | Producción | Sandbox | |:---|:---|:---| | **Cuentas y Acceso** | Requiere contrato comercial activo; credenciales emitidas en producción | **100% Gratuito y sin contrato**; cuenta independiente creada en Sandbox | | **Dominio API** | `{{url}}` (URL entregada al contratar el servicio) | `{{SANDBOX_URL}}` (`https://sandbox-api.matias-api.com/api/ubl2.1`) | | **Transmisión DIAN** | Transmisión SOAP en tiempo real a los servidores de la DIAN | Respuestas DIAN simuladas y mockeadas automáticamente | | **Firma Digital** | Certificado digital de firma emitido por entidad de certificación (ONAC) | Certificado digital de prueba (*Test Cert*) asignado automáticamente | | **Persistencia** | Datos persistidos en base de datos de producción | Aislamiento completo de base de datos | | **Autenticación** | JWT estándar | JWT estándar con credenciales de Sandbox | | **Endpoints Disponibles** | 20 módulos oficiales | **100% de paridad funcional** | | **Tasa de Cambio (TRM)** | Consulta en tiempo real de la TRM oficial | Valores adaptables y controlables | --- ## 🚀 Próximos Pasos * [Magic Values](https://docs.matias-api.com/docs/sandbox/magic-values.md) — Conoce todas las cabeceras para simular errores y respuestas DIAN. * [Certificado de Prueba](https://docs.matias-api.com/docs/sandbox/test-cert.md) — Especificaciones del certificado digital de pruebas. * [Colección Postman](https://docs.matias-api.com/docs/sandbox/postman.md) — Descarga la colección oficial para importar en Postman / Insomnia. --- # 🪄 Magic Values — MATIAS API Sandbox :::info 🌐 Enlaces Oficiales del Sandbox * **API Sandbox:** **`https://sandbox-api.matias-api.com/api/ubl2.1`** (marcador `{{SANDBOX_URL}}` en tus peticiones). * **Portal Web Sandbox:** **`https://sandbox-auth.matias-api.com/`** (portal de administración visual del sandbox). ::: --- El sandbox soporta **32 magic values** que permiten simular diferentes respuestas, códigos de validación y escenarios de rechazo de la DIAN sin necesidad de endpoints especiales ni alterar la integridad de tus esquemas de datos. --- ## 🛠️ Cómo Utilizarlos Envía el encabezado HTTP `X-Sandbox-Force-Status` en **cualquier** petición de generación o transmisión de documentos: * `POST /invoice` — Factura electrónica estándar, Sector Salud (Resolución 000948 de 2026) y Documento Equivalente POS Electrónico * `POST /notes/credit` — Nota crédito electrónica * `POST /notes/debit` — Nota débito electrónica * `POST /ds/document` — Documento Soporte electrónico * `POST /ds/adjustment-note` — Nota de ajuste a Documento Soporte * `POST /ep/payroll` — Nómina electrónica individual * `POST /ep/payroll/replace` — Reemplazo de nómina electrónica * `POST /ep/payroll/delete` — Eliminación de nómina electrónica * `POST /auto-increment/invoices` — Factura con consecutivo automático * `POST /auto-increment/credit-notes` — Nota Crédito automática * `POST /auto-increment/debit-notes` — Nota Débito automática * `POST /auto-increment/support-documents` — Documento Soporte automático * `POST /auto-increment/adjustment-notes` — Nota de Ajuste automática * `POST /auto-increment/pos-documents` — Documento POS automático :::note Notas del POS No existen rutas propias para el POS ni para sus notas (`/pos`, `/auto-increment/pos-credit-notes` o `/auto-increment/pos-debit-notes`). El POS se emite con `POST /invoice` o `POST /auto-increment/pos-documents`, y sus notas con `POST /notes/credit`, `POST /notes/debit`, `POST /auto-increment/credit-notes` o `POST /auto-increment/debit-notes`. ::: ```bash # Ejemplo 1: Forzar rechazo de validación en una factura curl -X POST {{SANDBOX_URL}}/invoice \ -H "Authorization: Bearer {token}" \ -H "X-Sandbox-Force-Status: ERROR_REJECTED" \ -H "Content-Type: application/json" \ -d @invoice.json # Ejemplo 2: Forzar error de CUDE en POS electrónico curl -X POST {{SANDBOX_URL}}/auto-increment/pos-documents \ -H "Authorization: Bearer {token}" \ -H "X-Sandbox-Force-Status: ERROR_POS_CUDE_INVALID" \ -H "Content-Type: application/json" \ -d @pos.json # Ejemplo 3: Forzar impuesto no permitido en Documento Soporte curl -X POST {{SANDBOX_URL}}/ds/document \ -H "Authorization: Bearer {token}" \ -H "X-Sandbox-Force-Status: ERROR_DS_TAX_INVALID" \ -H "Content-Type: application/json" \ -d @ds.json # Ejemplo 4: Forzar CUNE inválido en Nómina Electrónica curl -X POST {{SANDBOX_URL}}/ep/payroll \ -H "Authorization: Bearer {token}" \ -H "X-Sandbox-Force-Status: ERROR_PAYROLL_CUNE_INVALID" \ -H "Content-Type: application/json" \ -d @payroll.json ``` :::tip Comportamiento por Defecto (Happy Path) Si no se envía la cabecera `X-Sandbox-Force-Status`, el sandbox valida la sintaxis básica y devuelve automáticamente un estado `ACCEPTED` (aprobado por DIAN con código `00`). ::: --- ## 📋 Catálogo Completo de Magic Values (32) ### 1. ⚙️ Errores Generales y de Infraestructura (6) Aplican a **todos** los tipos de documento: | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_REJECTED` | Documento rechazado por validación de negocio | `B7B01` | Probar manejo de rechazos generales | | `ERROR_DUPLICATE` | Documento ya reportado previamente | `89` | Probar idempotencia y duplicados | | `ERROR_AUTH` | Error de autenticación del certificado | `IFE043` | Probar manejo de fallos en credenciales de firma | | `ERROR_SCHEMA` | Error de validación XSD del documento | `FAD06` | Probar control de esquemas XML inválidos | | `ERROR_500` | Error interno del servicio DIAN durante la transmisión. La API responde HTTP 500 con `code: "DOCUMENT_POSSIBLY_SENT"` | — | Probar el manejo de un posible envío: consultar el estado, no reintentar | | `ERROR_TIMEOUT` | Timeout de conexión con la DIAN durante la transmisión. La API responde HTTP 500 (no 504) con `code: "DOCUMENT_POSSIBLY_SENT"` | — | Probar el manejo de un posible envío: consultar el estado, no reintentar | :::warning `ERROR_500` y `ERROR_TIMEOUT` simulan un posible envío El fallo se simula **durante la transmisión**, cuando el documento ya pudo llegar a la DIAN. Por eso la API conserva el consecutivo y responde HTTP 500 con `code: "DOCUMENT_POSSIBLY_SENT"`, `data.uuid`, `data.document_number` y `reference`. La conducta correcta es **consultar el estado** con `GET /documents/{uuid}` o `GET /status`, no reintentar con otro número. En el envío masivo, estos items quedan en `POSSIBLY_SENT`. ::: ```json { "success": false, "message": "No se obtuvo una respuesta definitiva para el documento SETP990000001 y es posible que la DIAN lo haya recibido. No lo emita de nuevo: consulte su estado con el uuid del envío antes de cualquier reintento. Detalle: Error interno del servidor. DIAN Web Service error: HTTP 500 Internal Server Error (simulated by sandbox)", "reference": "3b0c6f5e-8d2a-4c7e-9f1b-2a6d5e4c3b21", "code": "DOCUMENT_POSSIBLY_SENT", "data": { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "document_number": "SETP990000001" } } ``` --- ### 2. 📄 Errores de Factura Electrónica (12) — Anexo Técnico 1.9 Aplicables a Facturación Estándar, Exportación y Sector Salud: | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_MATH_ROUNDING` | Error matemático en cálculo de impuestos | `FAS01A` | Probar descuadres decimales o aritméticos | | `ERROR_GROSS_TOTAL` | Inconsistencia en valor bruto de líneas vs cabecera | `FAD04` | Probar sumatorias incorrectas | | `ERROR_CUFE_MISMATCH` | El CUFE calculado difiere de la validación | `FAD12` | Probar cálculos criptográficos fallidos | | `ERROR_QR_INVALID` | Estructura de código QR inválida | `FAD13` | Probar fallos en el formato de la URL QR | | `ERROR_SIGNATURE` | Firma XAdES inválida (alterada o política incorrecta) | `IFE044` | Probar manipulación de XML post-firma | | `ERROR_EMAIL_MISMATCH` | Correo del receptor no coincide con el del RUT | `FAJ71` | Probar alertas de validación de identidad | | `ERROR_NIT_INVALID` | NIT del adquirente inexistente o DV incorrecto | `DAJ48` | Probar validación de datos maestros | | `ERROR_RESOLUTION_EXPIRED` | Resolución agotada o con fecha límite vencida | `FAD09B` | Probar control de agotamiento de resoluciones | | `ERROR_DATE_OUT_OF_RANGE` | Fecha de emisión fuera de los días permitidos | `FAD11` | Probar desfase temporal en la transmisión | | `ERROR_ALREADY_PROCESSED` | Documento procesado y aprobado previamente (regla 90). En sandbox llega el rechazo tal cual (ver nota) | `90` | Probar retransmisión de facturas aceptadas | | `ERROR_SOFTWARE_SECURITY` | SoftwareSecurityCode (PIN) no autorizado | `FAB27` | Probar bloqueos de software no autorizado | | `ERROR_ENVIRONMENT` | El schemeID no coincide con el entorno | `FAD07` | Probar envíos accidentales entre Prod y Test | :::info `ERROR_ALREADY_PROCESSED` en sandbox En sandbox la conciliación con la DIAN está desactivada: se recibe el rechazo tal cual (HTTP 200, `response.IsValid: "false"`, `StatusCode: "99"` con la regla `90`), sin los avisos `ALREADY_*` (`ALREADY_AUTHORIZED_RECOVERED`, `ALREADY_AUTHORIZED_OTHER_CONTENT`, `ALREADY_PROCESSED_NOT_CONFIRMED`) que sí aparecen en producción cuando la API concilia el número con la DIAN. Ver [Documento duplicado y regla 90](https://docs.matias-api.com/docs/response-json#documento-duplicado). ::: --- ### 3. 📑 Errores de Notas Crédito y Débito (3) — Anexo Técnico 1.9 | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_NC_DATE_MISMATCH` | Fecha de generación ≠ fecha de firma | `DAD09e` | Probar sincronización de fecha y firma en NC/ND | | `ERROR_NC_PAYMENT_METHOD` | Medio de pago no válido según catálogo DIAN | `FAN03` | Probar validación de catálogos de pago | | `ERROR_REFERENCE_ORPHAN` | Nota crédito apunta a un CUFE inexistente | `CBG04` | Probar trazabilidad en documentos referenciados | --- ### 4. 🧾 Errores de Documento Soporte Electrónico (3) — Resolución 000167 | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_DS_VENDOR_ID` | Identificación del vendedor inconsistente | `DSAJ25b` | Probar validación de tipo de documento del vendedor | | `ERROR_DS_TAX_INVALID` | Impuesto no permitido (ej. ReteICA) | `DSAY13` | Probar tributos no permitidos en Documento Soporte | | `ERROR_DS_LOCATION` | Municipio no corresponde al departamento | `DSAK25` | Probar consistencia geográfica DANE | --- ### 5. 🛠️ Errores de Notas de Ajuste a Documento Soporte (1) — Resolución 000167 | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_DS_NOTE_REF` | Documento soporte original referenciado no encontrado | `DSD04` | Probar trazabilidad de CUDS en notas de ajuste | --- ### 6. 🏪 Errores de POS / Documento Equivalente Electrónico (3) — Resolución 000165 | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_POS_VENDOR_RUT` | Razón social del emisor ≠ RUT | `FAJ43A` | Probar datos maestros del punto de venta | | `ERROR_POS_CUDE_INVALID` | CUDE inválido (SHA-384 no coincide) | `DEEAD01` | Probar cálculo criptográfico del CUDE | | `ERROR_POS_RESOLUTION` | Resolución POS no autorizada o vencida | `NSBG01` | Probar control de rangos de numeración POS | --- ### 7. 🔄 Errores de Notas de Ajuste POS (2) — Resolución 000165 | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_POS_NOTE_CUDE_REF` | CUDE del POS original no encontrado | `DEEAD02` | Probar trazabilidad del CUDE en notas POS | | `ERROR_POS_NOTE_NABA06` | Literal 195 no informado en nota POS | `NABA06` | Probar campos obligatorios en notas POS | --- ### 8. 👥 Errores de Nómina Electrónica (3) — Anexo Técnico Nómina V1.0 | Header Value | Comportamiento | Código DIAN | Caso de Uso | |---|---|:---:|---| | `ERROR_PAYROLL_DEPT` | Código de departamento inexistente | `NIE014` | Probar validaciones de tablas paramétricas DIAN | | `ERROR_PAYROLL_CUNE_INVALID` | CUNE de nómina calculado incorrectamente | `NIE024` | Probar cálculo criptográfico del CUNE nómina | | `ERROR_PAYROLL_PERIOD` | Período de liquidación inconsistente | `NIE016` | Probar validación de fechas de liquidación | --- ### 9. 🔐 Certificados Digitales (2) | Header Value | Comportamiento | Caso de Uso | |---|---|---| | `CERT_EXPIRED` | Fuerza un certificado expirado al firmar | Probar manejo y alertas de certificado vencido | | `CERT_NEAR_EXPIRY` | Certificado con validez restante menor a 5 días | Probar notificaciones preventivas de renovación | --- ## 🔄 Matriz de Compatibilidad con Tipos de Documento Todos los 32 magic values operan de manera transversal en **todos** los endpoints de emisión: | Tipo de Documento | Endpoint | Compatibilidad Magic Values | |---|---|:---:| | Factura Electrónica (Estándar / Salud) y POS | `POST /invoice` | ✅ Los 32 | | Nota Crédito Electrónica | `POST /notes/credit` | ✅ Los 32 | | Nota Débito Electrónica | `POST /notes/debit` | ✅ Los 32 | | Documento Soporte Electrónico | `POST /ds/document` | ✅ Los 32 | | Nota de Ajuste Documento Soporte | `POST /ds/adjustment-note` | ✅ Los 32 | | Nómina Electrónica Individual | `POST /ep/payroll` | ✅ Los 32 | | Reemplazo de Nómina | `POST /ep/payroll/replace` | ✅ Los 32 | | Eliminación de Nómina | `POST /ep/payroll/delete` | ✅ Los 32 | | Factura Auto-increment | `POST /auto-increment/invoices` | ✅ Los 32 | | NC Auto-increment | `POST /auto-increment/credit-notes` | ✅ Los 32 | | ND Auto-increment | `POST /auto-increment/debit-notes` | ✅ Los 32 | | DS Auto-increment | `POST /auto-increment/support-documents` | ✅ Los 32 | | Ajuste DS Auto-increment | `POST /auto-increment/adjustment-notes` | ✅ Los 32 | | POS Auto-increment | `POST /auto-increment/pos-documents` | ✅ Los 32 | --- ## 📦 Estructuras de Respuesta ### 1. Respuesta Exitosa — `ACCEPTED` (Sin Magic Value) La respuesta aceptada tiene la misma estructura que en producción (sin objeto `data`); se muestran los campos principales. Ver [Respuestas de la API](https://docs.matias-api.com/docs/response-json#respuesta-exitosa). ```json { "uuid": "dde72910-eb42-11ef-9b27-f02f74cac485", "message": "Solicitud procesada por la DIAN.", "send_to_queue": 0, "XmlDocumentKey": "a1b2c3d4e5f67890abcdef1234567890...", "response": { "IsValid": "true", "StatusCode": "00", "StatusDescription": "Procesado Correctamente.", "XmlDocumentKey": "a1b2c3d4e5f67890abcdef1234567890..." }, "XmlBase64Bytes": "", "AttachedDocument": {}, "qr": {}, "pdf": {}, "warnings": [], "success": true } ``` ### 2. Respuesta de Rechazo — `ERROR_POS_CUDE_INVALID` ```json { "response": { "ErrorMessage": { "string": [ "Regla: DEEAD01, Rechazo: El Código Único de Documento Equivalente (CUDE) calculado no coincide con la validación de la DIAN (SHA-384)." ] }, "IsValid": "false", "StatusCode": "99", "StatusDescription": "Validación Fallida", "StatusMessage": "Regla: DEEAD01, Rechazo: CUDE Inválido (SETP990000001)" } } ``` ### 3. Respuesta de Rechazo — `ERROR_DS_TAX_INVALID` ```json { "response": { "ErrorMessage": { "string": [ "Regla: DSAY13, Rechazo: El tributo reportado no está incluido en la tabla de tributos permitidos para Documento Soporte." ] }, "IsValid": "false", "StatusCode": "99", "StatusDescription": "Validación Fallida", "StatusMessage": "Regla: DSAY13, Rechazo: Impuesto no permitido en Documento Soporte (SETP990000001)" } } ``` --- ## ⚡ Reglas Operativas del Sandbox * **Aislamiento Estricto:** Los magic values **solo se evalúan** en el entorno sandbox (`{{SANDBOX_URL}}`). En producción, la cabecera `X-Sandbox-Force-Status` es descartada silenciosamente. * **Sin Contaminación de Payload:** No necesitas modificar el JSON del documento para probar errores; toda la simulación se controla mediante la cabecera HTTP. * **Eventos RADIAN:** Los endpoints de eventos RADIAN (`/events/*`) no requieren magic values; sus llamadas SOAP (`SendEvent`, `GetStatusEvents`, `GetXmlByDocumentKey`) son simuladas automáticamente devolviendo `ACCEPTED`. --- # Test Certificate — MATIAS API Sandbox Para facilitar el desarrollo, el entorno sandbox emite de forma automática un **certificado digital de prueba (Test Cert)** único para cada empresa que se registre en la plataforma. Este certificado es **estructuralmente equivalente** a un certificado de firma real emitido por una entidad de certificación abierta (CA) acreditada ante la ONAC en Colombia, pero está firmado bajo nuestra CA interna y simulada de desarrollo. --- ## Especificaciones Técnicas | Atributo / Campo | Valor Técnico de Prueba | |:---|:---| | **Formato** | PKCS#12 (`.p12` / binario) | | **Versión X.509** | v3 | | **Algoritmo de firma** | `sha256WithRSAEncryption` | | **Tamaño de Llave** | RSA 2048 bits | | **Issuer (Emisor)** | `CN=MATIAS SANDBOX TEST CA, O=MATIAS API, OU=Sandbox, C=CO` | | **Subject (Sujeto)** | `CN={NIT}{DV}, O={razón_social}, OU=Persona Juridica, C=CO` | | **Validez** | 1 año a partir del momento de generación | | **Uso de Llave (Key Usage)** | `digitalSignature`, `nonRepudiation`, `keyEncipherment` | | **Clave de Acceso (Passphrase)**| Única por empresa (consúltala dinámicamente vía `GET /certificate`) | --- ## Comportamiento del Certificado ### 1. Auto-Asignación El certificado de pruebas se genera y asocia a tu cuenta automáticamente en dos escenarios: * Al crear una nueva cuenta/empresa a través del endpoint de registro del sandbox. * Al ejecutar seeders de inicialización rápida en tu ambiente aislado. ### 2. Ciclo de Vida Homologado Todos los endpoints de gestión de certificados se comportan de forma **exactamente idéntica a producción**: | Endpoint API | Comportamiento en Sandbox | |:---|:---| | `POST /certificate` | Permite subir tu propio archivo de firma `.p12` de prueba. | | `GET /certificate` | Devuelve la metadata completa y la passphrase del certificado activo. | | `GET /certificate/expiration/{dni}` | Expone el cálculo de expiración real del Test Cert asignado. | | `PUT /certificate/{id}` | Permite actualizar la firma digital asociada. | | `DELETE /certificate/{id}` | Desvincula el certificado asignado. | ### 3. Tipos de Documento Firmados El certificado digital de prueba se utiliza de forma automática para firmar criptográficamente **todas** las tipologías de documentos electrónicos enviadas al sandbox: | Tipo de Documento Electrónico | Endpoint del API de Pruebas | |:---|:---| | Factura electrónica estándar / Salud (Res. 000948/2026) | `POST /invoice` | | Documento Equivalente POS Electrónico | `POST /pos` | | Nota crédito electrónica (estándar / salud) | `POST /notes/credit` | | Nota débito electrónica | `POST /notes/debit` | | Documento soporte electrónico | `POST /ds/document` | | Nota de ajuste a Documento Soporte | `POST /ds/adjustment-note` | | Nómina electrónica individual | `POST /ep/payroll` | | Reemplazo de nómina electrónica | `POST /ep/payroll/replace` | | Eliminación/Anulación de nómina | `POST /ep/payroll/delete` | | Factura auto-incrementable | `POST /auto-increment/invoices` | | NC auto-incrementable | `POST /auto-increment/credit-notes` | | ND auto-incrementable | `POST /auto-increment/debit-notes` | | DS auto-incrementable | `POST /auto-increment/support-documents` | | Nota de ajuste auto-incrementable | `POST /auto-increment/adjustment-notes` | | POS equivalente auto-incrementable | `POST /auto-increment/pos-documents` | --- ## Firma Estructural (XMLDSig) El XML resultante de la firma digital con el Test Cert del sandbox cuenta con todas las estructuras criptográficas requeridas: * Bloques `SignedInfo`, `SignatureValue`, `KeyInfo` y `X509Data` correctamente poblados. * Pasa de forma exitosa cualquier validador de esquemas XSD de la especificación UBL 2.1 de la DIAN. * :::info Rechazo DIAN Cualquier envío de este XML a servidores reales de la DIAN resultará en un rechazo inmediato debido a que la CA de emisión (`MATIAS SANDBOX TEST CA`) no forma parte de la cadena oficial de certificación digital ONAC. ::: --- ## Red de Seguridad en Producción Para garantizar que un certificado de pruebas del sandbox nunca sea transmitido por error a ambientes reales y productivos de la DIAN, el gateway de producción de MATIAS API cuenta con una capa de seguridad activa llamada `CertificateFingerprintGuard`: :::caution Protección Activa * **Validación de Huella:** Valida la firma del certificado mediante una base de datos local de huellas SHA-256 de desarrollo (`fingerprints.json`). * **Verificación de CN:** Examina que el atributo `issuer.CN` no coincida con el nombre `MATIAS SANDBOX TEST CA`. * **Bloqueo Preventivo:** Si el emisor o la firma corresponden al Test Cert en una llamada productiva, el API arrojará inmediatamente una excepción `RuntimeException` de seguridad, cancelando el envío del documento. ::: --- ## Descarga de la CA de Pruebas Si tu software o validador local de firmas requiere incorporar nuestra CA simulada al truststore para validar la firma de los documentos que recibes en el sandbox, puedes descargar el archivo de certificado raíz libremente: ```http {{SANDBOX_URL}}/pki/test-ca.crt ``` --- # 📮 Colección Postman — MATIAS API Sandbox Para acelerar la integración técnica y las pruebas de comportamiento en el sandbox, disponemos de una **Colección Postman oficial con 43 solicitudes** preconfiguradas que cubren tanto el flujo exitoso (*happy path*) como todos los escenarios de error simulables (*Magic Values*). --- ## 📥 Descarga e Importación [📮 Ver Documentación Interactiva en Postman](https://documenter.getpostman.com/view/8699065/2s9YyvBLby) [⬇️ Descargar Archivo JSON de Colección](https://docs.matias-api.com/sandbox-quickstart.postman_collection.json) ### Pasos para importar en Postman: 1. Abre tu aplicación Postman (Web o Desktop). 2. Haz clic en **Import** (esquina superior izquierda). 3. Arrastra o selecciona el archivo `sandbox-quickstart.postman_collection.json`. 4. Selecciona el Environment de Sandbox y comienza a realizar tus peticiones. --- ## 📁 Estructura de la Colección (43 Requests) La colección se encuentra estructurada en **11 carpetas lógicas**: | Carpeta | Requests | Descripción | |---|:---:|---| | **1. Producción** | 1 | Registro de cuenta en ambiente productivo. | | **2. Sandbox — Auth** | 2 | Login y generación de tokens de acceso (PAT) para el Sandbox. | | **3. Sandbox — Facturación** | 14 | Happy Path y **13 simulaciones de error (Magic Values)** para facturas. | | **4. Sandbox — PDF & Status** | 3 | Consulta asíncrona de estado, descarga de PDF y metadatos. | | **5. Sandbox — Notas Crédito/Débito** | 2 | Pruebas de notas y validaciones de fechas desfasadas. | | **6. Sandbox — Documento Soporte** | 2 | Documento Soporte electrónico y tributos no permitidos. | | **7. Sandbox — Nómina Electrónica** | 3 | Simulación de nómina, CUNE alterado y validación territorial. | | **8. Sandbox — Auto-Increment** | 2 | Pruebas de emisión con series y consecutivos automáticos. | | **9. Sandbox — POS Electrónico** | 5 | Documento Equivalente POS y notas de ajuste (NC/ND). | | **10. Sandbox — DS No Residente** | 2 | Documentos Soporte y notas para proveedores del exterior. | | **11. Sandbox — Eventos RADIAN** | 7 | Envío y consulta de eventos de factura (030, 031, 032, 033). | --- ## ⚙️ Variables de Entorno Recomendadas Configura un **Environment** en Postman con las siguientes variables para operar de forma dinámica: | Variable | Tipo | Valor Sugerido | Descripción | |:---|:---|:---|:---| | `url` | default | URL entregada al contratar | URL base de producción (en el sandbox: `https://sandbox-api.matias-api.com/api/ubl2.1`). | | `sandbox_url` | default | `https://sandbox-api.matias-api.com/api/ubl2.1` | URL base del Sandbox. | | `access_token` | secret | `eyJ...` | Token JWT obtenido en `/auth/login`. | | `pat_token` | secret | `1\|eyJ...` | Personal Access Token (PAT) persistente. | :::tip Scripts Automatizados en Tests La colección incluye scripts en la pestaña **Tests** del Login y Creación de PAT que extraen y guardan automáticamente los tokens en tus variables de entorno para que no tengas que copiarlos manualmente. ::: --- # 📚 Glosario Técnico :::info 📖 Referencia Normativa y Conceptual Este glosario contiene los **términos técnicos, normativas, acrónimos y conceptos principales** relacionados con la facturación electrónica, documentos equivalentes, nómina electrónica, RADIAN y el **Sector Salud (Resolución 000948 de 2026 / RIPS)** en Colombia. ::: ## 🗯️ Navegación Alfabética [A](https://docs.matias-api.com/docs/glossary#a) [B](https://docs.matias-api.com/docs/glossary#b) [C](https://docs.matias-api.com/docs/glossary#c) [D](https://docs.matias-api.com/docs/glossary#d) [E](https://docs.matias-api.com/docs/glossary#e) [F](https://docs.matias-api.com/docs/glossary#f) [G](https://docs.matias-api.com/docs/glossary#g) [H](https://docs.matias-api.com/docs/glossary#h) [I](https://docs.matias-api.com/docs/glossary#i) [J](https://docs.matias-api.com/docs/glossary#j) [L](https://docs.matias-api.com/docs/glossary#l) [M](https://docs.matias-api.com/docs/glossary#m) [N](https://docs.matias-api.com/docs/glossary#n) [O](https://docs.matias-api.com/docs/glossary#o) [P](https://docs.matias-api.com/docs/glossary#p) [Q](https://docs.matias-api.com/docs/glossary#q) [R](https://docs.matias-api.com/docs/glossary#r) [S](https://docs.matias-api.com/docs/glossary#s) [T](https://docs.matias-api.com/docs/glossary#t) [U](https://docs.matias-api.com/docs/glossary#u) [V](https://docs.matias-api.com/docs/glossary#v) [W](https://docs.matias-api.com/docs/glossary#w) [X](https://docs.matias-api.com/docs/glossary#x) [Z](https://docs.matias-api.com/docs/glossary#z) :::warning ⚠️ Nota Crítica: IDs de API vs Códigos DIAN **Esta es la distinción más importante para utilizar la API correctamente:** * **ID (Base de Datos API):** Número entero único usado en las peticiones JSON (ej: `type_document_id: 7` para Factura Electrónica). * **Code (DIAN):** Código normativo de la DIAN, usado exclusivamente para referencia regulatoria (ej: Code `01` = Factura de Venta). **🔑 REGLA FUNDAMENTAL:** En el cuerpo de las peticiones SIEMPRE utilizas el **ID numérico de la API**, NUNCA el código alfanumérico DIAN. ::: --- ## A ### Additional Document Reference (Documento Adicional de Referencia) **Definición**: Grupo de datos que contiene referencias a documentos comerciales o de soporte adicionales. * **Uso Obligatorio**: Facturas tipo 03 (Factura de Contingencia) para referenciar el documento de papel o talonario original. * **Estructura**: `number`, `uuid` (CUFE/CUDE), `scheme_name`, `date`, `code`. --- ### ADRES **Definición**: Administradora de los Recursos del Sistema General de Seguridad Social en Salud. Entidad pública adscrita al MinSalud que administra los recursos financieros del SGSSS y efectúa pagos/giros directos a prestadores y proveedores. --- ### Ambiente de Destino **Definición**: El entorno al cual se envía el documento electrónico. * **Producción (`1`)**: Ambiente real con validez fiscal y transmisión directa a la DIAN. * **Pruebas / Habilitación (`2`)**: Ambiente del set de pruebas para validar habilitación de software. --- ### Anticipo (`advance`) **Definición**: Valor monetario entregado con anterioridad a la prestación del servicio que se legaliza y descuenta en la factura electrónica en ejecución del contrato pactado. --- ### ARL **Definición**: Administradora de Riesgos Laborales. Entidad responsable de financiar y prestar servicios de salud derivados de accidentes de trabajo y enfermedades laborales. --- ## B ### Base Imponible **Definición**: Valor sobre el cual se calcula el porcentaje de impuesto (IVA, INC, Retención) aplicable a cada línea o al total del documento. --- ### Buzón de Recepción **Definición**: Dirección de correo única por empresa, con la forma `{identificador}@{dominio}`, a la que los proveedores envían sus facturas electrónicas para que la API las registre como recepciones. Se asigna al registrar la primera [persona de recepción](https://docs.matias-api.com/docs/glossary#persona-de-recepcion) y no cambia. Se consulta con `GET /events/mailbox/address`. Ver [Buzón de recepción](https://docs.matias-api.com/docs/endpoints/events-radian#buzon-recepcion). --- ## C ### Certificado Digital **Definición**: Archivo criptográfico PKCS#12 (`.p12` o `.pfx`) emitido por una entidad de certificación acreditada (ONAC) que garantiza la autoría, integridad y no repudio de los documentos XML transmitidos a la DIAN. --- ### Cobertura o Plan de Beneficios (`coverage`) **Definición**: Plan de beneficios en salud bajo el cual se prestan los servicios (ej. Plan de Beneficios en Salud - UPC, Plan Complementario, Pólizas de Salud, SOAT). --- ### Contingencia **Definición**: Mecanismo operativo que permite emitir facturas cuando se presentan fallas técnicas atribuibles a la DIAN (Contingencia Tipo 04) o al facturador (Contingencia Tipo 03). --- ### Copago (`copayment`) **Definición**: Aporte en dinero correspondiente a una parte del valor del servicio cubierto por el Plan de Beneficios en Salud, a cargo exclusivo de los beneficiarios del régimen contributivo. --- ### Correo Retenido **Definición**: Correo del [buzón de recepción](https://docs.matias-api.com/docs/glossary#buzon-de-recepcion) que llegó cuando el plan de pago de la empresa estaba vencido. Queda con `status` `IGNORED` y `reason_code` `PLAN_EXPIRED` (`is_held` = `true`), no consume cupo y se procesa automáticamente al renovar el plan si no han pasado más de 30 días desde su llegada (también con `POST /events/mailbox/{id}/retry`). Una suscripción de pago **cancelada** no retiene correos: quedan `IGNORED` sin retención. Ver [Correos retenidos](https://docs.matias-api.com/docs/endpoints/events-radian#correos-retenidos). --- ### CUCON **Definición**: Código Único de Facturación Electrónica en Contingencia. --- ### CUDE **Definición**: Código Único de Documento Electrónico. Cadena criptográfica SHA-384 que identifica unívocamente notas crédito, notas débito, documentos soporte y documentos equivalentes POS. --- ### CUFE **Definición**: Código Único de Facturación Electrónica. Cadena criptográfica generada mediante algoritmo SHA-384 que identifica de manera única e irrepetible cada factura electrónica de venta autorizada por la DIAN. --- ### CUNE **Definición**: Código Único de Nómina Electrónica. Identificador unívoco generado a partir de los datos de liquidación del Documento Soporte de Pago de Nómina Electrónica. --- ### Cuota Moderadora (`moderator_fee`) **Definición**: Pago monetario que regula la utilización de los servicios de salud y estimula su buen uso, a cargo de cotizantes y beneficiarios en el régimen contributivo. --- ## D ### DIAN **Definición**: Dirección de Impuestos y Aduanas Nacionales. Autoridad tributaria, aduanera y cambiaria de Colombia encargada de regular y validar los documentos fiscales electrónicos. --- ### Dígito Verificador (DV) **Definición**: Cifra numérica calculada mediante el algoritmo oficial módulo 11 de la DIAN que valida la integridad y congruencia del NIT. --- ### Documento Equivalente POS Electrónico **Definición**: Documento electrónico (tipo `20`) que reemplaza la tradicional tirilla de máquina registradora POS y otorga derecho a deducción de costos y gastos cuando identifica al adquirente. --- ### Documento Soporte Electrónico (DS) **Definición**: Documento electrónico (tipo `11`) expedido por el comprador para soportar transacciones con personas o proveedores no obligados a expedir factura de venta. --- ## E ### EPS **Definición**: Entidad Promotora de Salud. Entidad responsable de la afiliación, registro y recaudo de cotizaciones de los usuarios en el sistema de salud colombiano. --- ### Eventos RADIAN **Definición**: Mensajes electrónicos oficiales transmitidos a la DIAN para gestionar el ciclo de vida de la factura electrónica como título valor: * **Evento 030**: Acuse de recibo de la factura. * **Evento 031**: Reclamo de la factura (con `claim_code` del `01` al `04`). * **Evento 032**: Recibo de bienes y/o servicios. * **Evento 033**: Aceptación expresa. Solo aplican a **facturas electrónicas de venta a crédito**: las facturas de contado (o sin medio de pago) y las notas crédito/débito no admiten eventos. Ver [Eventos RADIAN](https://docs.matias-api.com/docs/endpoints/events-radian.md). --- ## F ### Factura Electrónica de Venta (FEV) **Definición**: Título valor digital que soporta transacciones de compraventa de bienes o prestación de servicios, validado previamente en tiempo real por la DIAN antes de su entrega al adquirente. --- ### Factura del Sector Salud **Definición**: Factura electrónica que incorpora el objeto regulatorio `health` conforme a la **Resolución 000948 de 2026**, integrando la información estructurada de atenciones de salud con los RIPS. --- ## G ### Generación de Documentos **Definición**: Proceso de estructuración y validación sintáctica del JSON de entrada para construir el XML conforme al estándar UBL 2.1 antes de la firma digital. --- ### Geolocalización DANE **Definición**: Codificación oficial de departamentos y municipios establecida por el Departamento Administrativo Nacional de Estadística (DANE) utilizada en los campos `city_id` y `department_id`. --- ## H ### Health (Objeto `health`) **Definición**: Objeto JSON opcional pero obligatorio para facturas del sector salud. Contiene información asistencial, periodos de atención, modalidad de pago, copagos y cuotas moderadoras. --- ## I ### ICA **Definición**: Impuesto de Industria y Comercio. Tributo territorial municipal que grava la realización de actividades industriales, comerciales o de servicios. --- ### INCOTERM **Definición**: Términos internacionales de comercio (ej. FOB, CIF, EXW) requeridos en facturas de exportación para definir responsabilidades entre comprador y vendedor. --- ### IPS **Definición**: Institución Prestadora de Servicios de Salud. Hospitales, clínicas, laboratorios y centros médicos habilitados para prestar servicios de salud. --- ### IVA **Definición**: Impuesto sobre las Ventas. Tributo nacional indirecto de valor agregado que grava la venta de bienes y la prestación de servicios (tarifas estándar: 0%, 5%, 19%). --- ## J ### JSON (JavaScript Object Notation) **Definición**: Formato estándar de intercambio de datos ligero y legible por humanos utilizado en toda la API de MATIAS para el envío y recepción de información. --- ## L ### Línea de Factura (`lines`) **Definición**: Array de ítems o conceptos facturados que detalla cantidades, precios unitarios, descuentos, tributos y descripciones del servicio o producto. --- ### Lote Masivo (Bulk) **Definición**: Conjunto de múltiples documentos electrónicos enviados de manera agrupada mediante `POST /bulk/documents` para procesamiento en cola asíncrona. --- ## M ### Magic Values **Definición**: Catálogo de 32 cabeceras HTTP especiales (`X-Sandbox-Force-Status`) utilizables en el ambiente Sandbox para simular rechazos, códigos de error y respuestas DIAN específicas. --- ### Modalidad de Pago (Salud) **Definición**: Mecanismo contractual de pago de servicios de salud entre prestadores y pagadores (ej. Pago por evento `01`, Pago por capitación `02`, Pago por conjunto integral `03`). --- ### Modo de Eventos (Automático / Manual) **Definición**: Configuración de la empresa (`send_events`) que define qué eventos RADIAN se generan sin intervención del usuario al registrar una factura a crédito (buzón o importación por Excel/CUFE). **Automático** (`true`): se crean y envían el `030`, el `032` y el `033`. **Manual** (`false`, valor inicial de una empresa nueva): no se crea ninguno, tampoco el `030`; el usuario los envía con `POST /events/send/{trackId}` o `POST /events/send-bulk`. Se cambia con `PUT /events/reception-people/mode`. --- ## N ### NIT **Definición**: Número de Identificación Tributaria asignado por la DIAN para la individualización de personas jurídicas y naturales en Colombia. --- ### Nómina Electrónica **Definición**: Documento soporte de pago de nómina electrónica y notas de ajuste que sustentan los costos deducibles derivados de pagos laborales. --- ### Nota Crédito **Definición**: Documento electrónico (tipo `5`) utilizado para anular facturas, conceder descuentos posteriores o registrar devoluciones de mercancías. --- ### Nota Débito **Definición**: Documento electrónico (tipo `4`) utilizado para registrar cargos adicionales, intereses de mora o aumentos de valor sobre facturas previamente emitidas. --- ## O ### ONAC **Definición**: Organismo Nacional de Acreditación de Colombia. Entidad responsable de acreditar a las Entidades de Certificación Digital que emiten certificados de firma válidos ante la DIAN. --- ### Operación de Facturación **Definición**: Código paramétrico (`operation_type_id`) que define el tipo de operación mercantil (ej. Estándar `1`, Servicios `2`, Salud `7`). --- ## P ### Pago Compartido (`shared_payments`) **Definición**: Montos recaudados por el prestador en planes voluntarios de salud (medicina prepagada, planes complementarios) a cargo del usuario o titular. --- ### Persona de Recepción **Definición**: Persona cuyos datos firman (acusan, reciben, aceptan o reclaman) los eventos RADIAN de la empresa. Una empresa puede tener varias, con una **por defecto**: la que firma los eventos automáticos del buzón y la que se usa cuando no se indica `reception_person_uuid`. La persona indicada al importar por Excel o CUFE firma solo los eventos automáticos de esa recepción; los envíos manuales usan la persona de su propia solicitud o la por defecto. Se administra con `/events/reception-people`. Ver [Personas de recepción](https://docs.matias-api.com/docs/endpoints/events-radian#personas-recepcion). --- ### Personal Access Token (PAT) **Definición**: Token de autenticación de larga duración (JWT estándar basado en Laravel Passport) que permite autenticar peticiones API desde sistemas ERP o servidores backend. --- ### P.O.S (Point of Sale) **Definición**: Punto de venta o caja registradora donde se originan transacciones comerciales directas con el consumidor final. --- ## Q ### QR Code (Código QR) **Definición**: Código bidimensional de respuesta rápida generado e insertado obligatoriamente en la representación gráfica (PDF) que enlaza al catálogo público de validación de la DIAN. --- ## R ### RADIAN **Definición**: Plataforma oficial administrada por la DIAN que registra y custodia los eventos y operaciones de factoring electrónico sobre facturas electrónicas de venta como título valor. --- ### RECEPTION_NOT_IN_PLAN **Definición**: Código de error (`error_code`) de la respuesta `403` que devuelve todo el módulo de recepción (`/events/*`: eventos RADIAN, importación, buzón y personas de recepción) cuando la empresa no tiene un plan de pago vigente (plan FREE, plan vencido o sin suscripción). Para una sub-cuenta cuenta el plan de su Casa de Software. Reemplaza al código `MAILBOX_NOT_IN_PLAN`, que el buzón ya no emite. Ver [Requisito de plan](https://docs.matias-api.com/docs/endpoints/events-radian#requisito-de-plan). --- ### REPS **Definición**: Registro Especial de Prestadores de Servicios de Salud del Ministerio de Salud y Protección Social. Contiene el código de habilitación (`provider_code`) de las IPS y profesionales del país. --- ### RIPS **Definición**: Registro Individual de Prestación de Servicios de Salud. Conjunto de datos estructurados de atenciones en salud que constituye el soporte obligatorio de la Factura Electrónica en salud conforme a la **Resolución 000948 de 2026**. --- ### RUT **Definición**: Registro Único Tributario. Mecanismo de identificación administrado por la DIAN que clasifica la responsabilidad fiscal de los contribuyentes. --- ## S ### Sector Salud (FEV Salud) **Definición**: Régimen especial de emisión de facturación electrónica obligatorio para prestadores de servicios y proveedores de tecnologías en salud reglamentado por el MinSalud y la DIAN. --- ### SOAT **Definición**: Seguro Obligatorio de Accidentes de Tránsito. Cobertura obligatoria para víctimas de accidentes de tránsito que se referencia con código `04` en `health.coverage` y requiere `health.policy_number`. --- ## T ### TRM **Definición**: Tasa Representativa del Mercado. Tipo de cambio oficial peso colombiano / dólar estadounidense certificado por la Superintendencia Financiera de Colombia. --- ## U ### UBL 2.1 **Definición**: Universal Business Language (versión 2.1). Estándar XML internacional adoptado por la DIAN para la estructura formal de los documentos tributarios electrónicos en Colombia. --- ### UPC (Unidad de Pago por Capitación) **Definición**: Valor anual que reconoce el Estado a las EPS por cada afiliado para garantizar la prestación de los servicios del Plan de Beneficios en Salud. --- ## V ### Validación Previa **Definición**: Mecanismo tecnológico mediante el cual la DIAN revisa y valida en tiempo real las reglas fiscales y aritméticas del XML antes de autorizar su entrega legal al adquirente. --- ## W ### Webhook **Definición**: Mecanismo de notificación HTTP asíncrono en tiempo real mediante el cual la API avisa a tu servidor cuando un documento o evento cambia de estado o completa su validación ante la DIAN. --- ## X ### XML **Definición**: Extensible Markup Language. Formato de texto estructurado en el que se codifican, firman y transmiten los documentos electrónicos ante la DIAN. --- ## Z ### ZIP (Contenedor AttachedDocument) **Definición**: Archivo comprimido `.zip` generado por la API que contiene el documento XML firmado electrónicamente junto con el `ApplicationResponse` oficial de aprobación emitido por la DIAN. --- ## 📊 Tablas de Referencia Rápida ### 📄 Identificadores de Tipos de Documento en la API (`type_document_id`) | ID (API) | Código DIAN | Tipo de Documento | Endpoint Principal | Identificador Criptográfico | |:---:|:---:|---|---|:---:| | **7** | `01` | Factura Electrónica de Venta (Estándar / Salud) | `POST /invoice` | CUFE | | **8** | `02` | Factura de Exportación | `POST /invoice` | CUFE | | **9** | `03` | Factura de Contingencia (Tipo 03) | `POST /invoice` | CUFE | | **10** | `04` | Factura de Contingencia (Tipo 04) | `POST /invoice` | CUFE | | **11** | `05` | Documento Soporte Electrónico | `POST /ds/document` | CUDS / CUDE | | **20** | `20` | Documento Equivalente POS Electrónico | `POST /invoice` | CUDE | | **5** | `91` | Nota Crédito Electrónica | `POST /notes/credit` | CUDE | | **4** | `92` | Nota Débito Electrónica | `POST /notes/debit` | CUDE | --- ### 🆔 Tipos de Identificación (`identity_document_id`) | ID (API) | Documento | |:---:|---| | **1** | Cédula de Ciudadanía | | **2** | NIT | | **3** | Cédula de Extranjería | | **4** | Tarjeta de Identidad | | **5** | Pasaporte | | **6** | Documento de Identificación Extranjero | | **10** | Sin identificación del exterior / Consumidor Final | --- ### 🏛️ Tipos de Organización (`type_organization_id`) | ID (API) | Organización | |:---:|---| | **1** | Persona Jurídica | | **2** | Persona Natural | --- 📅 **Versión:** 3.7.0 (Agosto 2026) • 📚 **Términos:** 150+ definiciones completas • 🎯 **Nivel:** ⭐⭐⭐ Referencia Técnica y Normativa --- # 🧮 Herramientas Interactivas Utiliza estas utilidades interactivas integradas en el navegador para calcular y validar datos fiscales antes de enviarlos a la API. --- ## 🔍 Validador de NIT Valida el dígito verificador de un NIT colombiano usando el algoritmo oficial módulo 11 de la DIAN. > _Herramienta interactiva «NITValidator»:_ disponible en la [versión web](https://docs.matias-api.com/docs/interactive-tools). --- ## ✅ Validador de Factura JSON Valida la estructura básica de una factura JSON y verifica cálculos importantes de impuestos y totales. > _Herramienta interactiva «JSONValidator»:_ disponible en la [versión web](https://docs.matias-api.com/docs/interactive-tools). --- ## 🧮 Calculadora de Totales Calcula automáticamente los totales de tu factura con descuentos, cargos e impuestos. > _Herramienta interactiva «TotalCalculator»:_ disponible en la [versión web](https://docs.matias-api.com/docs/interactive-tools). --- ## 📝 Guía de Uso 1. **Para NITs**: Usa el [Validador de NIT](https://docs.matias-api.com/docs/interactive-tools#validador-nit) para obtener el dígito de verificación correcto. 2. **Para facturas completas**: Pasa tu payload por el [Validador de Factura JSON](https://docs.matias-api.com/docs/interactive-tools#validador-factura-json) antes de invocar `POST /invoice`. 3. **Para sumatorias complejas**: Usa la [Calculadora de Totales](https://docs.matias-api.com/docs/interactive-tools#calculadora-totales) para cuadrar bases imponibles y totales a pagar. :::tip Paridad de Algoritmos Estas herramientas ejecutan exactamente las mismas reglas de validación que el motor de MATIAS API en el backend. :::