{
  "openapi": "3.1.0",
  "info": {
    "title": "FactuBridge",
    "description": "API REST de **FactuBridge** para la emisión y gestión de registros de facturación\nVeri\\*factu (Real Decreto 1007/2023).\n\n## Autenticación\n\nLos endpoints de facturación y consulta requieren una **API key** enviada como\ntoken Bearer en la cabecera HTTP:\n\n```\nAuthorization: Bearer <api_key>\n```\n\nLa API key se genera al crear el ObligadoTributario y puede rotarse desde el panel\nde administración.\n\n## Flujo básico\n\n1. **POST /v1/create** — Emite una factura o lote de facturas.\n   Devuelve un `uuid` de seguimiento.\n2. **GET /v1/status?uuid=\\<uuid\\>** — Consulta el estado del registro\n   (PENDIENTE → ENVIADO / ERROR).\n3. **POST /v1/cancel** — Anula una factura ya registrada ante la AEAT.\n4. **PUT /v1/modify** — Subsana un registro previamente rechazado.\n\n## Formatos de fecha\n\nTodos los campos de fecha aceptan `DD-MM-YYYY` (formato español)\no `YYYY-MM-DD` (ISO 8601).\n\n## Formato de error\n\nTodos los errores siguen la misma estructura:\n\n```json\n{ \"error\": \"Descripción del error\" }\n```\n\nLos errores de validación (400) incluyen además el campo `errores` con el detalle\ncampo a campo para facilitar la integración.\n",
    "contact": {
      "name": "Soporte FactuBridge",
      "email": "soporte@factubridge.es"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.factubridge.es",
      "description": "Producción"
    },
    {
      "url": "https://sandbox.factubridge.es",
      "description": "Sandbox (AEAT preproducción)"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health Check",
        "description": "Health check para monitorización.\n\nDevuelve 503 si el certificado del ColaboradorSocial está configurado\npero los ficheros no son legibles (el pod no debería recibir tráfico).",
        "operationId": "health_check_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "type": "object",
                  "title": "Response Health Check Health Get"
                }
              }
            }
          }
        }
      }
    },
    "/v1/create": {
      "post": {
        "tags": [
          "Facturas"
        ],
        "summary": "Crear factura nueva",
        "description": "Crea un registro de facturación nuevo. Responde inmediatamente con el QR.\n\nAdmite la cabecera opcional `Idempotency-Key` (ASCII imprimible, 1-80 caracteres; si es inválida → 400 `invalid_idempotency_key`): un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado con su estado actual y la cabecera de respuesta `Idempotent-Replayed: true`; la misma clave con un cuerpo distinto responde 422 `idempotency_key_reused_with_different_payload`. Si se envió clave y no hubo replay, la respuesta lleva `Idempotent-Replayed: false`.\n\nLa clave tiene prioridad sobre el control de duplicados por clave natural: un reintento con clave hace replay (200); un 409 con clave señala una colisión real de numeración (serie/número/fecha ya usados por otra operación), no un reintento.",
        "operationId": "crear_factura_v1_create_post",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422.",
              "title": "Idempotency-Key"
            },
            "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FacturaInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FacturaResponse"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Bad Request"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicadoFacturaResponse"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Internal Server Error"
          }
        }
      }
    },
    "/v1/create_bulk": {
      "post": {
        "tags": [
          "Facturas"
        ],
        "summary": "Crear facturas en lote",
        "description": "Crea entre 1 y 50 facturas en una sola llamada. Las facturas se encadenan en el orden del array. Si alguna factura es duplicada (409) la operación completa se cancela y no se crea ningún registro.\n\nAdmite la cabecera opcional `Idempotency-Key` (ASCII imprimible, 1-80 caracteres; si es inválida → 400 `invalid_idempotency_key`), que cubre el **lote completo**: un reintento con la misma clave y el mismo cuerpo devuelve el array completo en el orden original, con el estado actual de cada registro y la cabecera de respuesta `Idempotent-Replayed: true`; la misma clave con un cuerpo distinto responde 422 `idempotency_key_reused_with_different_payload`. Si se envió clave y no hubo replay, la respuesta lleva `Idempotent-Replayed: false`.\n\nCon clave, un 409 señala una colisión real de numeración (un reintento habría hecho replay), no un reintento. No existe un 409 de 'reintento concurrente en vuelo': los reintentos concurrentes se serializan en el servidor y el segundo recibe directamente el replay. Las claves no caducan, pero el cliente no debe depender de replays de más de 24 horas.\n\nSin cabecera, el comportamiento clásico queda intacto: el reintento del lote se apoya en su atomicidad y en el 409 con el registro existente de la primera factura, adoptable como éxito igual que en `/create` sin cabecera.",
        "operationId": "crear_facturas_bulk_v1_create_bulk_post",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422.",
              "title": "Idempotency-Key"
            },
            "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FacturaInput"
                },
                "title": "Facturas"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FacturaBulkResponse"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Bad Request"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicadoFacturaResponse"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Internal Server Error"
          }
        }
      }
    },
    "/v1/cancel": {
      "post": {
        "tags": [
          "Facturas"
        ],
        "summary": "Anular factura",
        "description": "Envía un RegistroAnulacion a la AEAT para anular una factura existente. Responde inmediatamente con el UUID y la huella del registro de anulación.\n\nAdmite la cabecera opcional `Idempotency-Key` (ASCII imprimible, 1-80 caracteres; si es inválida → 400 `invalid_idempotency_key`): un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado con su estado actual y la cabecera de respuesta `Idempotent-Replayed: true`; la misma clave con un cuerpo distinto responde 422 `idempotency_key_reused_with_different_payload`. Si se envió clave y no hubo replay, la respuesta lleva `Idempotent-Replayed: false`.",
        "operationId": "anular_factura_v1_cancel_post",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422.",
              "title": "Idempotency-Key"
            },
            "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnulacionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnulacionResponse"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Bad Request"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Internal Server Error"
          }
        }
      }
    },
    "/v1/modify": {
      "put": {
        "tags": [
          "Facturas"
        ],
        "summary": "Subsanar factura",
        "description": "Envía un RegistroAlta con Subsanacion=S para subsanar una factura previamente rechazada por la AEAT. El JSON de entrada es idéntico a /create más el campo rechazo_previo.\n\nAdmite la cabecera opcional `Idempotency-Key` (ASCII imprimible, 1-80 caracteres; si es inválida → 400 `invalid_idempotency_key`): un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado con su estado actual y la cabecera de respuesta `Idempotent-Replayed: true`; la misma clave con un cuerpo distinto responde 422 `idempotency_key_reused_with_different_payload`. Si se envió clave y no hubo replay, la respuesta lleva `Idempotent-Replayed: false`.",
        "operationId": "subsanar_factura_v1_modify_put",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422.",
              "title": "Idempotency-Key"
            },
            "description": "Clave de idempotencia opcional (ASCII imprimible, 1-80 caracteres). Un reintento con la misma clave y el mismo cuerpo devuelve el registro ya creado (cabecera de respuesta `Idempotent-Replayed: true`) en lugar de crear otro; la misma clave con un cuerpo distinto responde 422."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubsanacionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FacturaResponse"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Bad Request"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Internal Server Error"
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "tags": [
          "Consultas"
        ],
        "summary": "Estado registro",
        "description": "Consulta el estado de un registro de facturación por UUID",
        "operationId": "consultar_estado_registro_v1_status_get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "uuid",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "UUID del registro",
              "title": "Uuid"
            },
            "description": "UUID del registro"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistroEstado"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/registros": {
      "get": {
        "tags": [
          "Consultas"
        ],
        "summary": "Listar registros",
        "description": "Lista los registros de facturación del NIF autenticado",
        "operationId": "listar_registros_v1_registros_get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 1000,
              "minimum": 1,
              "description": "Número máximo de resultados (1-1000)",
              "default": 100,
              "title": "Limite"
            },
            "description": "Número máximo de resultados (1-1000)"
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "description": "Desplazamiento para paginación",
              "default": 0,
              "title": "Offset"
            },
            "description": "Desplazamiento para paginación"
          },
          {
            "name": "estado",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Filtrar por estado: pendiente, correcto, incorrecto, aceptado_con_errores, duplicado, anulado, error_servidor_aeat",
              "title": "Estado"
            },
            "description": "Filtrar por estado: pendiente, correcto, incorrecto, aceptado_con_errores, duplicado, anulado, error_servidor_aeat"
          },
          {
            "name": "fecha_desde",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "\\d{2}-\\d{2}-\\d{4}",
              "description": "Filtrar desde esta fecha de expedición (DD-MM-YYYY)",
              "examples": [
                "01-01-2026"
              ],
              "title": "Fecha Desde"
            },
            "description": "Filtrar desde esta fecha de expedición (DD-MM-YYYY)"
          },
          {
            "name": "fecha_hasta",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "\\d{2}-\\d{2}-\\d{4}",
              "description": "Filtrar hasta esta fecha de expedición (DD-MM-YYYY)",
              "examples": [
                "28-02-2026"
              ],
              "title": "Fecha Hasta"
            },
            "description": "Filtrar hasta esta fecha de expedición (DD-MM-YYYY)"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RegistroOut"
                  },
                  "title": "Response Listar Registros V1 Registros Get"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Bad Request"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Consultas"
        ],
        "summary": "Estado API",
        "description": "Estado de la API key e información del NIF",
        "operationId": "health_check_v1_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthOut"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v1/validate/nif": {
      "post": {
        "tags": [
          "Validación"
        ],
        "summary": "Pre-validar NIF/CIF/NIE en censo AEAT",
        "description": "POST /v1/validate/nif\n\nConsulta el censo AEAT (VNifV2) para un NIF español usando caché persistente.\nNo emite ninguna factura.\n\n- Si la validación está en caché y no ha expirado: responde inmediatamente.\n- Si AEAT no responde: fuente='servicio_no_disponible' (fail-open).\n- No devuelve HTTP 400 por resultado 'No identificado': es consulta, no\n  gating. El SIF decide en función del resultado devuelto.",
        "operationId": "validar_nif_v1_validate_nif_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidarNifRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidarNifResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v1/validate/vat": {
      "post": {
        "tags": [
          "Validación"
        ],
        "summary": "Pre-validar número de IVA intracomunitario en VIES",
        "description": "POST /v1/validate/vat\n\nConsulta el registro VIES para un número de IVA intracomunitario.\nNo emite ninguna factura.\n\n- Si el país no es de la UE: responde inmediatamente sin SOAP.\n- Si la validación está en caché: responde sin SOAP.\n- Si VIES no responde: fuente='servicio_no_disponible' (fail-open).",
        "operationId": "validar_vat_v1_validate_vat_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidarVatRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidarVatResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar webhooks",
        "description": "Devuelve todos los webhooks de la instalación autenticada.",
        "operationId": "listar_webhooks_v1_webhooks_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WebhookResponse"
                  },
                  "type": "array",
                  "title": "Response Listar Webhooks V1 Webhooks Get"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Crear webhook",
        "description": "Registra un nuevo webhook. Si se proporciona `secret`, se devuelve en claro **solo en esta respuesta** — guárdalo de forma segura.",
        "operationId": "crear_webhook_v1_webhooks_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponse"
                }
              }
            }
          },
          "422": {
            "description": "URL inválida o límite alcanzado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v1/webhooks/{webhook_id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Detalle de webhook",
        "operationId": "obtener_webhook_v1_webhooks__webhook_id__get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Webhook Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Modificar webhook",
        "description": "Modifica un webhook. Si se envía `secret`, rota el anterior y lo devuelve en claro **solo en esta respuesta**.",
        "operationId": "modificar_webhook_v1_webhooks__webhook_id__put",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Webhook Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Eliminar webhook",
        "operationId": "eliminar_webhook_v1_webhooks__webhook_id__delete",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Webhook Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "title": "Response Eliminar Webhook V1 Webhooks  Webhook Id  Delete"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/client-webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar webhooks de cliente",
        "description": "Devuelve todos los webhooks del cliente multi-NIF. Solo disponible para instalaciones con cliente_id.",
        "operationId": "listar_webhooks_cliente_v1_client_webhooks_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WebhookResponse"
                  },
                  "type": "array",
                  "title": "Response Listar Webhooks Cliente V1 Client Webhooks Get"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Crear webhook de cliente",
        "description": "Registra un nuevo webhook compartido por todos los NIFs del cliente. El `secret` se devuelve en claro **solo en esta respuesta**.",
        "operationId": "crear_webhook_cliente_v1_client_webhooks_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Content",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ]
      }
    },
    "/v1/client-webhooks/{webhook_id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Detalle de webhook de cliente",
        "operationId": "obtener_webhook_cliente_v1_client_webhooks__webhook_id__get",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Webhook Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Modificar webhook de cliente",
        "description": "Modifica un webhook de cliente. Si se envía `secret`, rota el anterior y lo devuelve en claro **solo en esta respuesta**.",
        "operationId": "modificar_webhook_cliente_v1_client_webhooks__webhook_id__put",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Webhook Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Eliminar webhook de cliente",
        "operationId": "eliminar_webhook_cliente_v1_client_webhooks__webhook_id__delete",
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Webhook Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "title": "Response Eliminar Webhook Cliente V1 Client Webhooks  Webhook Id  Delete"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Not Found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unprocessable Content"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AnulacionInput": {
        "properties": {
          "serie": {
            "type": "string",
            "maxLength": 60,
            "title": "Serie"
          },
          "numero": {
            "type": "string",
            "maxLength": 60,
            "title": "Numero"
          },
          "fecha_expedicion": {
            "type": "string",
            "format": "date",
            "title": "Fecha Expedicion",
            "description": "Fecha de emisión. Acepta DD-MM-YYYY o YYYY-MM-DD.",
            "examples": [
              "28-02-2026"
            ]
          },
          "rechazo_previo": {
            "type": "string",
            "pattern": "^(N|S)$",
            "title": "Rechazo Previo",
            "description": "S si la factura fue rechazada previamente por la AEAT",
            "default": "N"
          },
          "sin_registro_previo": {
            "type": "string",
            "pattern": "^(N|S)$",
            "title": "Sin Registro Previo",
            "description": "S si la factura no está registrada en la AEAT",
            "default": "N"
          },
          "incidencia": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Incidencia",
            "description": "Valor 'S' en caso de incidencia"
          }
        },
        "type": "object",
        "required": [
          "serie",
          "numero",
          "fecha_expedicion"
        ],
        "title": "AnulacionInput",
        "description": "Anulación de factura (POST /v1/cancel)."
      },
      "AnulacionResponse": {
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid",
            "title": "Uuid",
            "description": "Identificador único del registro de anulación"
          },
          "estado": {
            "type": "string",
            "title": "Estado",
            "description": "Estado del registro",
            "default": "Pendiente"
          },
          "huella": {
            "type": "string",
            "title": "Huella",
            "description": "Huella SHA-256 del registro de anulación"
          }
        },
        "type": "object",
        "required": [
          "uuid",
          "huella"
        ],
        "title": "AnulacionResponse",
        "description": "Respuesta inmediata al SIF tras recibir solicitud de anulación."
      },
      "CalificacionOperacionType": {
        "type": "string",
        "enum": [
          "S1",
          "S2",
          "N1",
          "N2"
        ],
        "title": "CalificacionOperacionType",
        "description": ":cvar S1: OPERACIÓN SUJETA Y NO EXENTA - SIN INVERSIÓN DEL SUJETO\n    PASIVO.\n:cvar S2: OPERACIÓN SUJETA Y NO EXENTA - CON INVERSIÓN DEL SUJETO\n    PASIVO\n:cvar N1: OPERACIÓN NO SUJETA ARTÍCULO 7, 14, OTROS.\n:cvar N2: OPERACIÓN NO SUJETA POR REGLAS DE LOCALIZACIÓN"
      },
      "ClaveTipoFacturaType": {
        "type": "string",
        "enum": [
          "F1",
          "F2",
          "R1",
          "R2",
          "R3",
          "R4",
          "R5",
          "F3"
        ],
        "title": "ClaveTipoFacturaType",
        "description": ":cvar F1: FACTURA (ART. 6, 7.2 Y 7.3 DEL RD 1619/2012)\n:cvar F2: FACTURA SIMPLIFICADA Y FACTURAS SIN IDENTIFICACIÓN DEL\n    DESTINATARIO ART. 6.1.D) RD 1619/2012\n:cvar R1: FACTURA RECTIFICATIVA (Art 80.1 y 80.2 y error fundado en\n    derecho)\n:cvar R2: FACTURA RECTIFICATIVA (Art. 80.3)\n:cvar R3: FACTURA RECTIFICATIVA (Art. 80.4)\n:cvar R4: FACTURA RECTIFICATIVA (Resto)\n:cvar R5: FACTURA RECTIFICATIVA EN FACTURAS SIMPLIFICADAS\n:cvar F3: FACTURA EMITIDA EN SUSTITUCIÓN DE FACTURAS SIMPLIFICADAS\n    FACTURADAS Y DECLARADAS"
      },
      "ClaveTipoRectificativaType": {
        "type": "string",
        "enum": [
          "S",
          "I"
        ],
        "title": "ClaveTipoRectificativaType",
        "description": ":cvar S: SUSTITUTIVA\n:cvar I: INCREMENTAL"
      },
      "CountryType2": {
        "type": "string",
        "enum": [
          "AF",
          "AL",
          "DE",
          "AD",
          "AO",
          "AI",
          "AQ",
          "AG",
          "SA",
          "DZ",
          "AR",
          "AM",
          "AW",
          "AU",
          "AT",
          "AZ",
          "BS",
          "BH",
          "BD",
          "BB",
          "BE",
          "BZ",
          "BJ",
          "BM",
          "BY",
          "BO",
          "BA",
          "BW",
          "BV",
          "BR",
          "BN",
          "BG",
          "BF",
          "BI",
          "BT",
          "CV",
          "KY",
          "KH",
          "CM",
          "CA",
          "CF",
          "CC",
          "CO",
          "KM",
          "CG",
          "CD",
          "CK",
          "KP",
          "KR",
          "CI",
          "CR",
          "HR",
          "CU",
          "TD",
          "CZ",
          "CL",
          "CN",
          "CY",
          "CW",
          "DK",
          "DM",
          "DO",
          "EC",
          "EG",
          "AE",
          "ER",
          "SK",
          "SI",
          "ES",
          "US",
          "EE",
          "ET",
          "FO",
          "PH",
          "FI",
          "FJ",
          "FR",
          "GA",
          "GM",
          "GE",
          "GS",
          "GH",
          "GI",
          "GD",
          "GR",
          "GL",
          "GU",
          "GT",
          "GG",
          "GN",
          "GQ",
          "GW",
          "GY",
          "HT",
          "HM",
          "HN",
          "HK",
          "HU",
          "IN",
          "ID",
          "IR",
          "IQ",
          "IE",
          "IM",
          "IS",
          "IL",
          "IT",
          "JM",
          "JP",
          "JE",
          "JO",
          "KZ",
          "KE",
          "KG",
          "KI",
          "KW",
          "LA",
          "LS",
          "LV",
          "LB",
          "LR",
          "LY",
          "LI",
          "LT",
          "LU",
          "XG",
          "MO",
          "MK",
          "MG",
          "MY",
          "MW",
          "MV",
          "ML",
          "MT",
          "FK",
          "MP",
          "MA",
          "MH",
          "MU",
          "MR",
          "YT",
          "UM",
          "MX",
          "FM",
          "MD",
          "MC",
          "MN",
          "ME",
          "MS",
          "MZ",
          "MM",
          "NA",
          "NR",
          "CX",
          "NP",
          "NI",
          "NE",
          "NG",
          "NU",
          "NF",
          "NO",
          "NC",
          "NZ",
          "IO",
          "OM",
          "NL",
          "BQ",
          "PK",
          "PW",
          "PA",
          "PG",
          "PY",
          "PE",
          "PN",
          "PF",
          "PL",
          "PT",
          "PR",
          "QA",
          "GB",
          "RW",
          "RO",
          "RU",
          "SB",
          "SV",
          "WS",
          "AS",
          "KN",
          "SM",
          "SX",
          "PM",
          "VC",
          "SH",
          "LC",
          "ST",
          "SN",
          "RS",
          "SC",
          "SL",
          "SG",
          "SY",
          "SO",
          "LK",
          "SZ",
          "ZA",
          "SD",
          "SS",
          "SE",
          "CH",
          "SR",
          "TH",
          "TW",
          "TZ",
          "TJ",
          "PS",
          "TF",
          "TL",
          "TG",
          "TK",
          "TO",
          "TT",
          "TN",
          "TC",
          "TM",
          "TR",
          "TV",
          "UA",
          "UG",
          "UY",
          "UZ",
          "VU",
          "VA",
          "VE",
          "VN",
          "VG",
          "VI",
          "WF",
          "YE",
          "DJ",
          "ZM",
          "ZW",
          "QU",
          "XB",
          "XU",
          "XN"
        ],
        "title": "CountryType2"
      },
      "DuplicadoFacturaResponse": {
        "properties": {
          "error": {
            "type": "string",
            "title": "Error"
          },
          "registro": {
            "$ref": "#/components/schemas/FacturaResponse"
          }
        },
        "type": "object",
        "required": [
          "error",
          "registro"
        ],
        "title": "DuplicadoFacturaResponse",
        "description": "409 cuando la factura ya existe — incluye datos del registro existente."
      },
      "ErrorResponse": {
        "properties": {
          "error": {
            "type": "string",
            "title": "Error"
          }
        },
        "type": "object",
        "required": [
          "error"
        ],
        "title": "ErrorResponse",
        "description": "Respuesta de error estándar"
      },
      "Especial": {
        "properties": {
          "cupon": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^S$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cupon",
            "description": "Indica que la factura rectificativa (R1/R5) corresponde a un cupón. Solo 'S'."
          },
          "factura_simplificada_art_7273": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^S$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Factura Simplificada Art 7273",
            "description": "Factura emitida al amparo de los artículos 72 y 73 del Reglamento de Facturación (simplificadas con datos del destinatario). Solo para F1/F3/R1-R4. Valor: 'S'."
          },
          "factura_sin_identif_destinatario_art_61d": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^S$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Factura Sin Identif Destinatario Art 61D",
            "description": "Factura simplificada sin identificación del destinatario (art. 61.1.d del Reglamento). Solo para F2/R5. Valor: 'S'."
          },
          "emitida_por_tercero_o_destinatario": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(T|D)$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Emitida Por Tercero O Destinatario",
            "description": "Indica si la factura fue emitida por un tercero (T) o por el propio destinatario (D). Si T, son obligatorios `nombre_tercero` y (`nif_tercero` o `id_otro_tercero`)."
          },
          "nombre_tercero": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 120
              },
              {
                "type": "null"
              }
            ],
            "title": "Nombre Tercero",
            "description": "Nombre o razón social del tercero emisor. Requerido si `emitida_por_tercero_o_destinatario='T'`."
          },
          "nif_tercero": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nif Tercero",
            "description": "NIF del tercero emisor. Requerido (junto a `nombre_tercero`) si `emitida_por_tercero_o_destinatario='T'` y tiene NIF español."
          },
          "id_otro_tercero": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IdOtroTercero"
              },
              {
                "type": "null"
              }
            ],
            "description": "Identificación del tercero emisor para no residentes sin NIF español. Alternativo a `nif_tercero`."
          },
          "macrodato": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^S$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Macrodato",
            "description": "PDF sección 10: campo obligatorio si |ImporteTotal| >= 100.000.000,00€. Solo se permite el valor 'S'."
          }
        },
        "type": "object",
        "title": "Especial",
        "description": "Holds optional special indicators and third-party issuer information.\n\nMany fields are permitted only for specific invoice types (see OpenAPI). All\nconstraints should be enforced at business validation stage where context\n(tipo_factura) is available."
      },
      "FacturaBulkResponse": {
        "properties": {
          "registros": {
            "items": {
              "$ref": "#/components/schemas/FacturaResponse"
            },
            "type": "array",
            "title": "Registros",
            "description": "Resultado de cada factura en el mismo orden que la petición"
          },
          "total": {
            "type": "integer",
            "title": "Total",
            "description": "Número total de registros creados"
          }
        },
        "type": "object",
        "required": [
          "registros",
          "total"
        ],
        "title": "FacturaBulkResponse",
        "description": "Respuesta de POST /create_bulk: una entrada por factura del lote."
      },
      "FacturaInput": {
        "properties": {
          "serie": {
            "type": "string",
            "maxLength": 60,
            "title": "Serie",
            "description": "Serie de la factura. Solo ASCII imprimibles (32-126), sin \" ' < > =."
          },
          "numero": {
            "type": "string",
            "maxLength": 60,
            "title": "Numero",
            "description": "Número de la factura dentro de la serie. Solo ASCII imprimibles (32-126), sin \" ' < > =."
          },
          "fecha_expedicion": {
            "type": "string",
            "format": "date",
            "title": "Fecha Expedicion",
            "description": "Fecha de expedición. Acepta DD-MM-YYYY o YYYY-MM-DD.",
            "examples": [
              "28-02-2026"
            ]
          },
          "fecha_operacion": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fecha Operacion",
            "description": "Fecha de operación si difiere de la expedición. Acepta DD-MM-YYYY o YYYY-MM-DD.",
            "examples": [
              "28-02-2026"
            ]
          },
          "tipo_factura": {
            "$ref": "#/components/schemas/ClaveTipoFacturaType",
            "description": "Tipo de factura. Valores: F1 (completa), F2 (simplificada, sin destinatario, máx. 3.000 €), F3 (sustitución de simplificadas), R1 (rectificativa art. 80.1/2/6 y error fundado en derecho), R2 (concurso de acreedores), R3 (deudas incobrables), R4 (resto de rectificativas), R5 (rectificativa simplificada). Regla destinatario: F1/F3/R1-R4 exigen `nombre` + (`nif` o `id_otro`); F2/R5 lo prohíben. Para rectificar una F2 usar siempre R5, nunca R1-R4."
          },
          "descripcion": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1,
            "title": "Descripcion",
            "description": "Descripción de la operación o concepto de la factura."
          },
          "lineas": {
            "items": {
              "$ref": "#/components/schemas/LineaFactura"
            },
            "type": "array",
            "maxItems": 12,
            "minItems": 1,
            "title": "Lineas",
            "description": "Líneas de detalle fiscal. Máximo 12 por factura; cada línea agrupa importes con el mismo tipo impositivo."
          },
          "importe_total": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              }
            ],
            "title": "Importe Total",
            "description": "Importe total de la factura (con IVA). Debe coincidir con la suma de las líneas dentro de un margen de ±10 €."
          },
          "nif": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nif",
            "description": "NIF/CIF/NIE del destinatario (empresa o autónomo español). Requerido en F1/F3/R1-R4 junto con `nombre`. Prohibido en F2/R5."
          },
          "id_otro": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IdOtro"
              },
              {
                "type": "null"
              }
            ],
            "description": "Identificación alternativa del destinatario para no residentes o entidades sin NIF español. Alternativo a `nif`."
          },
          "nombre": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 120
              },
              {
                "type": "null"
              }
            ],
            "title": "Nombre",
            "description": "Nombre o razón social del destinatario. Requerido en F1/F3/R1-R4 junto con `nif` o `id_otro`."
          },
          "validar_destinatario": {
            "type": "boolean",
            "title": "Validar Destinatario",
            "description": "Si `true`, prevalida el destinatario contra el censo de la AEAT (VNifV2) o VIES antes de emitir. Por defecto `false`: el censo es una conveniencia, no el gate regulatorio (el gate real es AEAT en el envío). Activar para detectar NIF erróneos antes de enviar.",
            "default": false
          },
          "tipo_rectificativa": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ClaveTipoRectificativaType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Obligatorio en facturas rectificativas (R1-R5). S: por sustitución (requiere `importe_rectificativa`). I: por diferencias."
          },
          "importe_rectificativa": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ImporteRectificativaInput"
              },
              {
                "type": "null"
              }
            ],
            "description": "Importes de la factura original que se rectifica. Obligatorio cuando `tipo_rectificativa='S'`."
          },
          "facturas_rectificadas": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Facturas Rectificadas",
            "description": "Lista de facturas originales que rectifica esta factura. Solo permitido en R1-R5."
          },
          "facturas_sustituidas": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Facturas Sustituidas",
            "description": "Lista de facturas simplificadas que sustituye esta factura F3."
          },
          "incidencia": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Incidencia",
            "description": "Indicador de incidencia. Valor 'S' si aplica."
          },
          "especial": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Especial"
              },
              {
                "type": "null"
              }
            ]
          },
          "cuota_total": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cuota Total",
            "description": "PDF sección 16: suma de cuotas (CuotaRepercutida + CuotaRecargoEquivalencia) de todas las líneas. Si se informa, se valida contra la suma calculada (±10€, aceptado con errores en AEAT)."
          },
          "num_registro_acuerdo_facturacion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Num Registro Acuerdo Facturacion",
            "description": "Número de registro del acuerdo de facturación. Si se informa, exime de la validación del importe máximo de facturas simplificadas F2."
          }
        },
        "type": "object",
        "required": [
          "serie",
          "numero",
          "fecha_expedicion",
          "tipo_factura",
          "descripcion",
          "lineas",
          "importe_total"
        ],
        "title": "FacturaInput",
        "description": "Schema de entrada EXACTO para creación de facturas.\n\nDocstrings and comments intentionally verbose for auditability. For cross-checking,\nsee the original Verifactu OpenAPI spec.",
        "example": {
          "descripcion": "Prestación de servicios",
          "fecha_expedicion": "2024-11-15",
          "importe_total": "242.00",
          "lineas": [
            {
              "base_imponible": "200.00",
              "cuota_repercutida": "42.00",
              "tipo_impositivo": "21"
            }
          ],
          "nif": "A15022510",
          "nombre": "Cliente SL",
          "numero": "234634",
          "serie": "A",
          "tipo_factura": "F1"
        }
      },
      "FacturaResponse": {
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid",
            "title": "Uuid",
            "description": "Identificador único del registro"
          },
          "estado": {
            "type": "string",
            "title": "Estado",
            "description": "Estado del registro",
            "default": "Pendiente"
          },
          "url": {
            "type": "string",
            "title": "Url",
            "description": "URL de verificación del código QR"
          },
          "qr": {
            "type": "string",
            "title": "Qr",
            "description": "Código QR en base64"
          },
          "huella": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Huella",
            "description": "Huella o hash del registro"
          }
        },
        "type": "object",
        "required": [
          "uuid",
          "url",
          "qr"
        ],
        "title": "FacturaResponse",
        "description": "Respuesta inmediata al SIF tras recibir factura (igual que Verifactu)"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "HealthOut": {
        "properties": {
          "estado": {
            "type": "string",
            "title": "Estado",
            "description": "Estado de la instalación SIF: operativo / error"
          },
          "nif": {
            "type": "string",
            "title": "Nif",
            "description": "NIF del ObligadoTributario vinculado a la API key"
          },
          "entorno": {
            "type": "string",
            "title": "Entorno",
            "description": "Entorno AEAT activo: pruebas o produccion"
          }
        },
        "type": "object",
        "required": [
          "estado",
          "nif",
          "entorno"
        ],
        "title": "HealthOut"
      },
      "IdOperacionesTrascendenciaTributariaType": {
        "type": "string",
        "enum": [
          "01",
          "02",
          "03",
          "04",
          "05",
          "06",
          "07",
          "08",
          "09",
          "10",
          "11",
          "14",
          "15",
          "17",
          "18",
          "19",
          "20",
          "21"
        ],
        "title": "IdOperacionesTrascendenciaTributariaType"
      },
      "IdOtro": {
        "properties": {
          "codigo_pais": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CountryType2"
              },
              {
                "type": "null"
              }
            ],
            "description": "ISO3166-1 alpha-2 country code. Required unless id_type == '02'."
          },
          "id_type": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PersonaFisicaJuridicaIdtypeType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Identifier type."
          },
          "id": {
            "type": "string",
            "maxLength": 20,
            "title": "Id",
            "description": "Identifier (max 20 chars)."
          }
        },
        "type": "object",
        "required": [
          "id"
        ],
        "title": "IdOtro",
        "description": "Identifier object alternative to NIF.\n\nFields\n- codigo_pais: ISO3166-1 alpha-2 country code. Required except when id_type == '02'.\n- id_type:\n    - VALUE_02: NIF-IVA\n    - VALUE_03: Pasaporte\n    - VALUE_04: IDEnPaisResidencia\n    - VALUE_05: Certificado Residencia\n    - VALUE_06: Otro documento Probatorio\n    - VALUE_07: No Censado\n- id: the identifier string (max 20 chars)."
      },
      "IdOtroTercero": {
        "properties": {
          "id_type": {
            "type": "string",
            "pattern": "^(02|03|04|05|06)$",
            "title": "Id Type"
          },
          "codigo_pais": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Codigo Pais"
          },
          "id": {
            "type": "string",
            "title": "Id"
          }
        },
        "type": "object",
        "required": [
          "id_type",
          "id"
        ],
        "title": "IdOtroTercero",
        "description": "Identifier for a third party (used when invoice is issued by a third)."
      },
      "ImporteRectificativaInput": {
        "properties": {
          "base_rectificada": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,12}|(?=[\\d.]{1,15}0*$)\\d{0,12}\\.\\d{0,2}0*$)"
              }
            ],
            "title": "Base Rectificada",
            "description": "Base imponible de la factura original que se rectifica."
          },
          "cuota_rectificada": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,12}|(?=[\\d.]{1,15}0*$)\\d{0,12}\\.\\d{0,2}0*$)"
              }
            ],
            "title": "Cuota Rectificada",
            "description": "Cuota repercutida de la factura original que se rectifica."
          },
          "cuota_recargo_rectificado": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,12}|(?=[\\d.]{1,15}0*$)\\d{0,12}\\.\\d{0,2}0*$)"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cuota Recargo Rectificado",
            "description": "Cuota de recargo de equivalencia de la factura original (si aplica)."
          }
        },
        "type": "object",
        "required": [
          "base_rectificada",
          "cuota_rectificada"
        ],
        "title": "ImporteRectificativaInput",
        "description": "Validación de entrada para datos de rectificación.\n\n- base_rectificada: base imponible rectificada\n- cuota_rectificada: cuota repercutida rectificada\n- cuota_recargo_rectificado: cuota recargo equivalencia rectificada (opcional)"
      },
      "ImpuestoType": {
        "type": "string",
        "enum": [
          "01",
          "02",
          "03",
          "05"
        ],
        "title": "ImpuestoType",
        "description": ":cvar VALUE_01: Impuesto sobre el Valor Añadido (IVA)\n:cvar VALUE_02: Impuesto sobre la Producción, los Servicios y la\n    Importación (IPSI) de Ceuta y Melilla\n:cvar VALUE_03: Impuesto General Indirecto Canario (IGIC)\n:cvar VALUE_05: Otros"
      },
      "LineaFactura": {
        "properties": {
          "base_imponible": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              }
            ],
            "title": "Base Imponible",
            "description": "Base imponible. Pattern: (+|-)d{1,12}(\\.d{0,2})?"
          },
          "tipo_impositivo": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,3}|(?=[\\d.]{1,6}0*$)\\d{0,3}\\.\\d{0,2}0*$)"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tipo Impositivo",
            "description": "Tipo impositivo de la línea. Obligatorio si calificacion_operacion es S1 y base_imponible_a_coste no está cumplimentado. Si impuesto = 01 (IVA) valores permitidos: 0, 2, 4, 5, 7.5, 10, 21."
          },
          "cuota_repercutida": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cuota Repercutida",
            "description": "Cuota repercutida (si aplica)."
          },
          "impuesto": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ImpuestoType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Default: '01' Enum: '01' '02' '03' '05'Tipo de impuesto. Los valores permitidos son:01: Impuesto sobre el Valor Añadido (IVA)02: Impuesto sobre la Producción, los Servicios y la Importación (IPSI) de Ceuta y Melilla03: Impuesto General Indirecto Canario (IGIC)05: Otros",
            "default": "01"
          },
          "calificacion_operacion": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CalificacionOperacionType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Enum: 'S1' 'S2' 'N1' 'N2'. Calificación de la operación. Solo se puede informar si el campo operacion_exenta no se cumplimenta. En ese caso, por defecto se usará el valor S1.S1: Operación sujeta y no exenta - sin inversión del sujeto pasivo.S2: Operación sujeta y no exenta - con inversión del sujeto pasivo.N1: Operación no sujeta (art. 7, 14, otros).N2: Operación no sujeta por reglas de localización.",
            "default": "S1"
          },
          "clave_regimen": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IdOperacionesTrascendenciaTributariaType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Enum: '01' '02' '03' '04' '05' '06' '07' '08' '09' '10' '11' '14' '15' '17' '18' '19' '20'Clave que identifica el tipo de régimen del IVA/IGIC. Permitido únicamente cuando impuesto = 01 (IVA) o impuesto = 03 (IGIC). En estos casos el valor por defecto es 01.01: Operación de régimen general.02: Exportación.03: Operaciones a las que se aplique el régimen especial de bienes usados, objetos de arte, antigüedades y objetos de colección.04: Régimen especial del oro de inversión.05: Régimen especial de las agencias de viajes.06: Régimen especial grupo de entidades en IVA o IGIC (Nivel Avanzado)07: Régimen especial del criterio de caja.08: Operaciones sujetas al IPSI/IVA o IGIC.09: Facturación de las prestaciones de servicios de agencias de viaje que actúan como mediadoras en nombre y por cuenta ajena(D.A.4ª RD1619/ 2012)10: Cobros por cuenta de terceros de honorarios profesionales o de derechos derivados de la propiedad industrial, de autor u otros por cuenta de sus socios, asociados o colegiados efectuados por sociedades, asociaciones, colegios profesionales u otras entidades que realicen estas funciones de cobro.11: Operaciones de arrendamiento de local de negocio.14: Factura con IVA o IGIC pendiente de devengo en certificaciones de obra cuyo destinatario sea una Administración Pública.15: Factura con IVA o IGIC pendiente de devengo en operaciones de tracto sucesivo.17: Operación acogida a alguno de los regímenes previstos en el Capítulo XI del Título IX(OSS e IOSS) o régimen especial de comerciante minorista18: Recargo de equivalencia o régimen especial del pequeño empresario o profesional.19: Operaciones de actividades incluidas en el Régimen Especial de Agricultura, Ganadería y Pesca(REAGYP) u operaciones interiores exentas por aplicación artículo 25 Ley 19 / 199420: Régimen simplificado"
          },
          "operacion_exenta": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/OperacionExentaType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Enum: 'E1' 'E2' 'E3' 'E4' 'E5' 'E6'. Tipo de operación exenta. En caso de estar cumplimentado, no podrá informarse de los campos tipo_impositivo, cuota_repercutida, tipo_recargo_equivalencia y cuota_recargo_equivalencia. Valores permitidos son (BOE-A-1992-28740):                E1: Exenta por artículo 20                E2: Exenta por artículo 21                E3: Exenta por artículo 22                E4: Exenta por artículo 24                E5: Exenta por artículo 25                E6: Otros"
          },
          "base_imponible_a_coste": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Base Imponible A Coste",
            "description": "Base imponible a coste de la linea. Este campo solo puede estar cumplimentado si la clave_regimen es = 06 o impuesto = 02 (IPSI) o impuesto = 05 (Otros)."
          },
          "tipo_recargo_equivalencia": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*(?:\\d{0,3}|(?=[\\d.]{1,6}0*$)\\d{0,3}\\.\\d{0,2}0*$)"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tipo Recargo Equivalencia",
            "description": "- Si Impuesto = '01' (IVA) o no se cumplimenta (considerándose '01' - IVA) y CalificacionOperacion = 'S1':   - Solo se permiten TipoRecargoEquivalencia = 0; 0,26; 0,5; 0,62; 1;   1,4; 1,75; 5,2 (valores que indican el tanto por ciento).- Si TipoImpositivo es 21 sólo se admitirán TipoRecargoEquivalencia = 5,2 ó 1,75.- Si TipoImpositivo es 10 sólo se admitirá TipoRecargoEquivalencia = 1,4.- Si TipoImpositivo es 7,5 sólo se admitirá TipoRecargoEquivalencia = 1.   ✓ Si FechaOperacion (FechaExpedicionFactura de la agrupación IDFactura   si no se informa FechaOperacion) es mayor o igual que 1 de octubre de   2024 y menor o igual que 31 de diciembre de 2024 se admitirá el   TipoRecargoEquivalencia = 1.- Si tipo impositivo es 5:   ✓ Si FechaOperacion (FechaExpedicionFactura de la agrupación IDFactura   si no se informa FechaOperacion) es igual o inferior al 31 de diciembre   de 2022, solo se admitirá TipoRecargoEquivalencia = 0,5.   ✓ Si FechaOperacion (FechaExpedicionFactura de la agrupación IDFactura   si no se informa FechaOperacion) es mayor o igual que 1 de enero de   2023 y menor o igual que 30 de septiembre de 2024, solo se admitirá   TipoRecargoEquivalencia = 0,62.- Si TipoImpositivo es 4 sólo se admitirá TipoRecargoEquivalencia = 0,5.- Si TipoImpositivo es 2 sólo se admitirá TipoRecargoEquivalencia = 0,26.   ✓ Si FechaOperacion (FechaExpedicionFactura de la agrupación IDFactura   si no se informa FechaOperacion) es mayor o igual que 1 de octubre de   2024 y menor o igual que 31 de diciembre de 2024 se admitirá el   TipoRecargoEquivalencia = 0,26.- Si tipo impositivo es 0:   ✓ Si FechaOperacion (FechaExpedicionFactura de la agrupación IDFactura   si no se informa FechaOperacion) es mayor o igual que 1 de enero de   2023 y menor o igual que 30 de septiembre de 2024, solo se admitirá   TipoRecargoEquivalencia = 0."
          },
          "cuota_recargo_equivalencia": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cuota Recargo Equivalencia",
            "description": "Cuota recargo equivalencia."
          }
        },
        "type": "object",
        "required": [
          "base_imponible"
        ],
        "title": "LineaFactura",
        "description": "A single invoice line.\n\nFields\n- base_imponible: base taxable amount (required)\n- tipo_impositivo: VAT rate or amount applied (optional)\n- cuota_repercutida: VAT amount (optional)\n- impuesto\n- calificacion_operacion\n- clave_regimen IdOperacionesTranscendenciaTributariaType\n- operacion_exenta: when the operation is exempt (then no tipos/cuotas allowed)\n- base_imponible_a_coste\n- tipo_recargo_equivalencia: recargo equivalencia VAT rate (optional)\n- cuota_recargo_equivalencia: recargo equivalencia amount (optional)"
      },
      "OperacionExentaType": {
        "type": "string",
        "enum": [
          "E1",
          "E2",
          "E3",
          "E4",
          "E5",
          "E6",
          "E7",
          "E8"
        ],
        "title": "OperacionExentaType"
      },
      "PersonaFisicaJuridicaIdtypeType": {
        "type": "string",
        "enum": [
          "02",
          "03",
          "04",
          "05",
          "06",
          "07"
        ],
        "title": "PersonaFisicaJuridicaIdtypeType",
        "description": ":cvar VALUE_02: NIF-IVA\n:cvar VALUE_03: Pasaporte\n:cvar VALUE_04: IDEnPaisResidencia\n:cvar VALUE_05: Certificado Residencia\n:cvar VALUE_06: Otro documento Probatorio\n:cvar VALUE_07: No Censado"
      },
      "RegistroEstado": {
        "properties": {
          "nif": {
            "type": "string",
            "title": "Nif",
            "description": "NIF del ObligadoTributario emisor"
          },
          "serie": {
            "type": "string",
            "title": "Serie",
            "description": "Serie de la factura"
          },
          "numero": {
            "type": "string",
            "title": "Numero",
            "description": "Número de la factura"
          },
          "fecha_expedicion": {
            "type": "string",
            "format": "date",
            "title": "Fecha Expedicion",
            "description": "Fecha de expedición (YYYY-MM-DD)"
          },
          "tipo_operacion": {
            "type": "string",
            "title": "Tipo Operacion",
            "description": "Tipo de operación: A (Alta) o AN (Anulación)"
          },
          "estado": {
            "type": "string",
            "title": "Estado",
            "description": "Estado del registro: PENDIENTE, ENVIADO, ACEPTADO, RECHAZADO, ERROR"
          },
          "url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url",
            "description": "URL de verificación del código QR AEAT"
          },
          "qr": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Qr",
            "description": "Código QR en base64"
          },
          "codigo_error": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Codigo Error",
            "description": "Código de error AEAT si procede"
          },
          "mensaje_error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Mensaje Error",
            "description": "Mensaje de error AEAT si procede"
          },
          "estado_registro_duplicado": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Estado Registro Duplicado",
            "description": "Indica si el registro fue marcado como duplicado por AEAT"
          }
        },
        "type": "object",
        "required": [
          "nif",
          "serie",
          "numero",
          "fecha_expedicion",
          "tipo_operacion",
          "estado"
        ],
        "title": "RegistroEstado",
        "description": "Estado de un registro de facturación (GET /status?uuid=...)"
      },
      "RegistroOut": {
        "properties": {
          "uuid": {
            "type": "string",
            "title": "Uuid",
            "description": "Identificador único del registro"
          },
          "serie": {
            "type": "string",
            "title": "Serie",
            "description": "Serie de la factura"
          },
          "numero": {
            "type": "string",
            "title": "Numero",
            "description": "Número de la factura"
          },
          "fecha_expedicion": {
            "type": "string",
            "title": "Fecha Expedicion",
            "description": "Fecha de expedición (DD-MM-YYYY)"
          },
          "tipo_operacion": {
            "type": "string",
            "title": "Tipo Operacion",
            "description": "Tipo de operación: A (Alta) o AN (Anulación)"
          },
          "estado": {
            "type": "string",
            "title": "Estado",
            "description": "Estado: PENDIENTE, ENVIADO, ACEPTADO, RECHAZADO, ERROR"
          },
          "importe_total": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Importe Total",
            "description": "Importe total de la factura"
          },
          "huella": {
            "type": "string",
            "title": "Huella",
            "description": "Huella SHA-256 encadenada del registro"
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At",
            "description": "Fecha y hora de creación (ISO 8601)"
          }
        },
        "type": "object",
        "required": [
          "uuid",
          "serie",
          "numero",
          "fecha_expedicion",
          "tipo_operacion",
          "estado",
          "huella"
        ],
        "title": "RegistroOut"
      },
      "SubsanacionInput": {
        "properties": {
          "serie": {
            "type": "string",
            "maxLength": 60,
            "title": "Serie",
            "description": "Serie de la factura. Solo ASCII imprimibles (32-126), sin \" ' < > =."
          },
          "numero": {
            "type": "string",
            "maxLength": 60,
            "title": "Numero",
            "description": "Número de la factura dentro de la serie. Solo ASCII imprimibles (32-126), sin \" ' < > =."
          },
          "fecha_expedicion": {
            "type": "string",
            "format": "date",
            "title": "Fecha Expedicion",
            "description": "Fecha de expedición. Acepta DD-MM-YYYY o YYYY-MM-DD.",
            "examples": [
              "28-02-2026"
            ]
          },
          "fecha_operacion": {
            "anyOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ],
            "title": "Fecha Operacion",
            "description": "Fecha de operación si difiere de la expedición. Acepta DD-MM-YYYY o YYYY-MM-DD.",
            "examples": [
              "28-02-2026"
            ]
          },
          "tipo_factura": {
            "$ref": "#/components/schemas/ClaveTipoFacturaType",
            "description": "Tipo de factura. Valores: F1 (completa), F2 (simplificada, sin destinatario, máx. 3.000 €), F3 (sustitución de simplificadas), R1 (rectificativa art. 80.1/2/6 y error fundado en derecho), R2 (concurso de acreedores), R3 (deudas incobrables), R4 (resto de rectificativas), R5 (rectificativa simplificada). Regla destinatario: F1/F3/R1-R4 exigen `nombre` + (`nif` o `id_otro`); F2/R5 lo prohíben. Para rectificar una F2 usar siempre R5, nunca R1-R4."
          },
          "descripcion": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1,
            "title": "Descripcion",
            "description": "Descripción de la operación o concepto de la factura."
          },
          "lineas": {
            "items": {
              "$ref": "#/components/schemas/LineaFactura"
            },
            "type": "array",
            "maxItems": 12,
            "minItems": 1,
            "title": "Lineas",
            "description": "Líneas de detalle fiscal. Máximo 12 por factura; cada línea agrupa importes con el mismo tipo impositivo."
          },
          "importe_total": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              }
            ],
            "title": "Importe Total",
            "description": "Importe total de la factura (con IVA). Debe coincidir con la suma de las líneas dentro de un margen de ±10 €."
          },
          "nif": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nif",
            "description": "NIF/CIF/NIE del destinatario (empresa o autónomo español). Requerido en F1/F3/R1-R4 junto con `nombre`. Prohibido en F2/R5."
          },
          "id_otro": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/IdOtro"
              },
              {
                "type": "null"
              }
            ],
            "description": "Identificación alternativa del destinatario para no residentes o entidades sin NIF español. Alternativo a `nif`."
          },
          "nombre": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 120
              },
              {
                "type": "null"
              }
            ],
            "title": "Nombre",
            "description": "Nombre o razón social del destinatario. Requerido en F1/F3/R1-R4 junto con `nif` o `id_otro`."
          },
          "validar_destinatario": {
            "type": "boolean",
            "title": "Validar Destinatario",
            "description": "Si `true`, prevalida el destinatario contra el censo de la AEAT (VNifV2) o VIES antes de emitir. Por defecto `false`: el censo es una conveniencia, no el gate regulatorio (el gate real es AEAT en el envío). Activar para detectar NIF erróneos antes de enviar.",
            "default": false
          },
          "tipo_rectificativa": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ClaveTipoRectificativaType"
              },
              {
                "type": "null"
              }
            ],
            "description": "Obligatorio en facturas rectificativas (R1-R5). S: por sustitución (requiere `importe_rectificativa`). I: por diferencias."
          },
          "importe_rectificativa": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ImporteRectificativaInput"
              },
              {
                "type": "null"
              }
            ],
            "description": "Importes de la factura original que se rectifica. Obligatorio cuando `tipo_rectificativa='S'`."
          },
          "facturas_rectificadas": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Facturas Rectificadas",
            "description": "Lista de facturas originales que rectifica esta factura. Solo permitido en R1-R5."
          },
          "facturas_sustituidas": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Facturas Sustituidas",
            "description": "Lista de facturas simplificadas que sustituye esta factura F3."
          },
          "incidencia": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Incidencia",
            "description": "Indicador de incidencia. Valor 'S' si aplica."
          },
          "especial": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Especial"
              },
              {
                "type": "null"
              }
            ]
          },
          "cuota_total": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cuota Total",
            "description": "PDF sección 16: suma de cuotas (CuotaRepercutida + CuotaRecargoEquivalencia) de todas las líneas. Si se informa, se valida contra la suma calculada (±10€, aceptado con errores en AEAT)."
          },
          "num_registro_acuerdo_facturacion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Num Registro Acuerdo Facturacion",
            "description": "Número de registro del acuerdo de facturación. Si se informa, exime de la validación del importe máximo de facturas simplificadas F2."
          },
          "rechazo_previo": {
            "type": "string",
            "pattern": "^(N|S|X)$",
            "title": "Rechazo Previo",
            "default": "N"
          }
        },
        "type": "object",
        "required": [
          "serie",
          "numero",
          "fecha_expedicion",
          "tipo_factura",
          "descripcion",
          "lineas",
          "importe_total"
        ],
        "title": "SubsanacionInput",
        "description": "Subsanación de factura (PUT /modify)",
        "example": {
          "descripcion": "Prestación de servicios",
          "fecha_expedicion": "2024-11-15",
          "importe_total": "242.00",
          "lineas": [
            {
              "base_imponible": "200.00",
              "cuota_repercutida": "42.00",
              "tipo_impositivo": "21"
            }
          ],
          "nif": "A15022510",
          "nombre": "Cliente SL",
          "numero": "234634",
          "serie": "A",
          "tipo_factura": "F1"
        }
      },
      "ValidarNifRequest": {
        "properties": {
          "nif": {
            "type": "string",
            "maxLength": 10,
            "minLength": 9,
            "title": "Nif",
            "description": "NIF/CIF/NIE"
          },
          "nombre": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 120
              },
              {
                "type": "null"
              }
            ],
            "title": "Nombre",
            "description": "Nombre o razón social para validación parcial. Recomendado para personas físicas."
          }
        },
        "type": "object",
        "required": [
          "nif"
        ],
        "title": "ValidarNifRequest"
      },
      "ValidarNifResponse": {
        "properties": {
          "nif": {
            "type": "string",
            "title": "Nif"
          },
          "resultado": {
            "type": "string",
            "title": "Resultado",
            "description": "Literal de VNifV2: 'Identificado' | 'Identificado-Baja' | 'Identificado-Revocado' | 'No identificado-similar' | 'No identificado' | 'No procesado'. 'No verificado' si fuente='servicio_no_disponible'."
          },
          "fuente": {
            "type": "string",
            "title": "Fuente",
            "description": "'cache' | 'aeat' | 'servicio_no_disponible'"
          },
          "consultado_at": {
            "type": "string",
            "title": "Consultado At",
            "description": "ISO 8601 UTC"
          },
          "expires_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expires At",
            "description": "ISO 8601 UTC. None si fuente='servicio_no_disponible'"
          },
          "aviso": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Aviso"
          }
        },
        "type": "object",
        "required": [
          "nif",
          "resultado",
          "fuente",
          "consultado_at"
        ],
        "title": "ValidarNifResponse"
      },
      "ValidarVatRequest": {
        "properties": {
          "codigo_pais": {
            "type": "string",
            "maxLength": 2,
            "minLength": 2,
            "title": "Codigo Pais",
            "description": "ISO 3166-1 alpha-2 (p. ej. 'DE', 'FR')"
          },
          "numero_iva": {
            "type": "string",
            "maxLength": 20,
            "minLength": 1,
            "title": "Numero Iva",
            "description": "Número de IVA sin prefijo de país"
          }
        },
        "type": "object",
        "required": [
          "codigo_pais",
          "numero_iva"
        ],
        "title": "ValidarVatRequest"
      },
      "ValidarVatResponse": {
        "properties": {
          "codigo_pais": {
            "type": "string",
            "title": "Codigo Pais"
          },
          "numero_iva": {
            "type": "string",
            "title": "Numero Iva"
          },
          "valido": {
            "type": "boolean",
            "title": "Valido"
          },
          "nombre": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Nombre"
          },
          "direccion": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Direccion"
          },
          "fuente": {
            "type": "string",
            "title": "Fuente",
            "description": "'cache' | 'vies' | 'servicio_no_disponible'"
          },
          "es_intracomunitario": {
            "type": "boolean",
            "title": "Es Intracomunitario"
          },
          "consultado_at": {
            "type": "string",
            "title": "Consultado At",
            "description": "ISO 8601 UTC"
          },
          "expires_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expires At",
            "description": "ISO 8601 UTC. None si fuente='servicio_no_disponible'"
          },
          "aviso": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Aviso"
          }
        },
        "type": "object",
        "required": [
          "codigo_pais",
          "numero_iva",
          "valido",
          "fuente",
          "es_intracomunitario",
          "consultado_at"
        ],
        "title": "ValidarVatResponse"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "WebhookCreate": {
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2083,
            "minLength": 1,
            "format": "uri",
            "title": "Url",
            "description": "URL HTTPS del endpoint receptor del webhook"
          },
          "secret": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 256,
                "minLength": 8
              },
              {
                "type": "null"
              }
            ],
            "title": "Secret",
            "description": "Secret para firma HMAC-SHA256. Se devuelve solo en esta respuesta; guárdalo de forma segura."
          },
          "activo": {
            "type": "boolean",
            "title": "Activo",
            "description": "Si el webhook está activo desde el inicio",
            "default": true
          }
        },
        "type": "object",
        "required": [
          "url"
        ],
        "title": "WebhookCreate",
        "description": "Cuerpo para crear un nuevo webhook."
      },
      "WebhookCreateResponse": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id",
            "description": "Identificador único del webhook"
          },
          "url": {
            "type": "string",
            "title": "Url",
            "description": "URL del endpoint receptor"
          },
          "activo": {
            "type": "boolean",
            "title": "Activo",
            "description": "Si el webhook está activo y recibirá notificaciones"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At",
            "description": "Fecha y hora de creación (ISO 8601)"
          },
          "updated_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Updated At",
            "description": "Fecha y hora de la última modificación (ISO 8601)"
          },
          "secret": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Secret",
            "description": "Secret HMAC en claro. Solo visible en este momento."
          }
        },
        "type": "object",
        "required": [
          "id",
          "url",
          "activo",
          "created_at"
        ],
        "title": "WebhookCreateResponse",
        "description": "Respuesta de creación o rotación de secret.\n\nEl campo secret solo se devuelve aquí; no es recuperable posteriormente."
      },
      "WebhookResponse": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "title": "Id",
            "description": "Identificador único del webhook"
          },
          "url": {
            "type": "string",
            "title": "Url",
            "description": "URL del endpoint receptor"
          },
          "activo": {
            "type": "boolean",
            "title": "Activo",
            "description": "Si el webhook está activo y recibirá notificaciones"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At",
            "description": "Fecha y hora de creación (ISO 8601)"
          },
          "updated_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Updated At",
            "description": "Fecha y hora de la última modificación (ISO 8601)"
          }
        },
        "type": "object",
        "required": [
          "id",
          "url",
          "activo",
          "created_at"
        ],
        "title": "WebhookResponse",
        "description": "Respuesta estándar de webhook (sin secret)."
      },
      "WebhookUpdate": {
        "properties": {
          "url": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2083,
                "minLength": 1,
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url",
            "description": "Nueva URL HTTPS"
          },
          "secret": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 256,
                "minLength": 8
              },
              {
                "type": "null"
              }
            ],
            "title": "Secret",
            "description": "Nuevo secret (rota el anterior). Devuelto solo en esta respuesta."
          },
          "activo": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Activo",
            "description": "Activar o desactivar el webhook"
          }
        },
        "type": "object",
        "title": "WebhookUpdate",
        "description": "Cuerpo para modificar un webhook existente."
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  },
  "tags": [
    {
      "name": "Facturas",
      "description": "Emisión, anulación y subsanación de registros de facturación Veri\\*factu."
    },
    {
      "name": "Consultas",
      "description": "Consulta de registros y estado de la instalación SIF."
    },
    {
      "name": "Webhooks",
      "description": "Notificaciones automáticas al SIF cuando AEAT responde a un envío.\n\nEvitan el polling constante: en cuanto FactuBridge recibe la respuesta de AEAT,\nhace un `POST` a todas las URLs de webhook configuradas para esa instalación.\n\n## Payload\n\nLista con el resultado de cada registro del lote:\n\n```json\n[\n  {\n    \"uuid\": \"b018ced3-...\",\n    \"nif\": \"B86561412\",\n    \"serie\": \"A\",\n    \"numero\": \"1234\",\n    \"fecha_expedicion\": \"20-04-2026\",\n    \"operacion\": \"Alta\",\n    \"url\": \"https://www1.agenciatributaria.gob.es/wlpl/...\",\n    \"estado\": \"Correcto\"\n  },\n  {\n    \"uuid\": \"f4504c29-...\",\n    \"nif\": \"B86561412\",\n    \"serie\": \"A\",\n    \"numero\": \"1235\",\n    \"fecha_expedicion\": \"20-04-2026\",\n    \"operacion\": \"Alta\",\n    \"url\": \"https://www1.agenciatributaria.gob.es/wlpl/...\",\n    \"estado\": \"Incorrecto\",\n    \"codigo_error\": 2005,\n    \"mensaje_error\": \"El campo ImporteTotal tiene un valor incorrecto...\"\n  }\n]\n```\n\n## Firma HMAC-SHA256\n\nSi el webhook tiene `secret` configurado, cada notificación incluye la cabecera:\n\n```\nX-Factubridge-Signature: <hmac-sha256-hex>\nX-Factubridge-Timestamp: <unix-timestamp>\n```\n\nPara verificar la autenticidad del mensaje:\n\n1. Obtén el cuerpo exacto del `POST` recibido (sin modificar).\n2. Calcula `HMAC-SHA256(secret, body)` en hexadecimal.\n3. Compara con el valor de `X-Factubridge-Signature`.\n\nSi coinciden, el mensaje es auténtico y no fue alterado en tránsito.\n\nEl campo `X-Factubridge-Timestamp` puede usarse opcionalmente para descartar\nnotificaciones con más de N minutos de antigüedad.\n\n## Webhooks de instalación vs. webhooks de cliente\n\n- **`/v1/webhooks`** — Webhooks por instalación SIF (un NIF). Cada instalación\n  gestiona los suyos de forma independiente.\n- **`/v1/client-webhooks`** — Webhooks compartidos por `cliente_id` (SIFs\n  multi-NIF: gestorías, ERPs que gestionan varios obligados). Un único webhook\n  recibe notificaciones de todos los NIFs del cliente; el campo `nif` en cada\n  elemento del payload identifica de qué obligado proviene la factura.\n  Solo disponible para instalaciones con `cliente_id` configurado.\n\n## Política de entrega\n\n- Reintentos: hasta 3 intentos por webhook (configurable).\n- Timeout: 10 segundos por intento.\n- Fail-open: un fallo de entrega no bloquea el procesamiento de facturas.\n"
    }
  ]
}
