> ## Documentation Index
> Fetch the complete documentation index at: https://developers.ipsofactura.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Timbrar un CFDI en XML

> Recibe un CFDI 4.0 **sin sellar** — el XML que ya genera tu ERP — y devuelve el comprobante timbrado. El reparto de trabajo es: **tú armas el documento, Ipsofactura lo sella y lo timbra**. La llave privada del CSD está en custodia nuestra, así que el sellado no puede ocurrir de tu lado.

El XML se manda en `xml_base64` (base64 de los bytes UTF-8 del XML; se aceptan saltos de línea). Ipsofactura lo valida contra los XSD del SAT, lo sella con el CSD del emisor y lo timbra por **el mismo pipeline** que `POST /cfdi/timbrar`.

**El documento es la fuente de la verdad.** Ipsofactura completa lo que el XML omite (folio consecutivo por emisor + serie si no trae `Folio`, datos del emisor si faltan) pero no sobrescribe lo que sí trae. La ventana del SAT (72 h hacia atrás, 5 min hacia adelante) se mide contra la `Fecha` **del documento**, no contra la hora en que nos llega: un ERP que despacha una cola de ayer tiene que re-fechar el XML.

**El `TipoDeComprobante` decide la ruta.** `I` y `E` se timbran como factura; `P` (con su complemento `pago20:Pagos`) se procesa como complemento de pago, resolviendo los CFDI relacionados, la parcialidad y el saldo insoluto desde los pagos ya registrados — los valores que el documento traiga en esos campos se recalculan.

**Qué se rechaza con 400:**
* `Sello`, `NoCertificado` o `Certificado` presentes → `UNSUPPORTED_CFDI_CONTENT`. El sellado es nuestro y no re-firmamos un documento sellado en otro lado: si tú sellaste el CFDI con tu propio CSD, mándalo a `POST /cfdi/timbrar-sellado`.
* `cfdi:Addenda`, o un `cfdi:Complemento` que no sea Recepción de Pagos 2.0 (Carta Porte, Comercio Exterior, …) → `UNSUPPORTED_CFDI_CONTENT`. Preferimos rechazar antes que entregarte un CFDI legalmente distinto al que mandaste.
* XML mal formado o raíz que no es un `cfdi:Comprobante` 4.0 → `INVALID_XML`.
* Incumple los esquemas del SAT → `XSD_VALIDATION_ERROR`, con hasta tres errores (elemento, atributo, línea y columna) en `message`.
* `xml_base64` no es base64 → `INVALID_BASE64`.

**Tamaño máximo: 2 MB** de XML decodificado (`REQUEST_TOO_LARGE`, 413). Dos órdenes de magnitud arriba del CFDI más grande que hemos visto.

**`idempotency_key`** (opcional, ≤ 100 caracteres; también se acepta en la cabecera `Idempotency-Key`): un reenvío con la misma llave devuelve la respuesta del primer intento con la cabecera `Idempotency-Replayed: true`, en lugar de timbrar otra vez. La misma llave con un documento distinto es 422 `IDEMPOTENCY_KEY_REUSED`: el segundo documento no se timbra, y tampoco se te devuelve el CFDI del primero. Sin llave, la deduplicación de un `I` o `E` sigue siendo serie + folio + RFC emisor + tipo, que responde 409.




## OpenAPI

````yaml /openapi.json post /cfdi/timbrar-xml
openapi: 3.1.0
info:
  description: API de timbrado CFDI 4.0 de Palenca.
  title: Ipsofactura API
  version: v1
servers:
  - description: Producción
    url: https://api.ipsofactura.com
  - description: Sandbox (pruebas, sin validez fiscal)
    url: https://sandbox.api.ipsofactura.com
security:
  - apiKeyAuth: []
tags:
  - description: Timbrado, complemento de pago, cancelación y descarga de CFDIs.
    name: CFDI
  - description: Alta y consulta de empresas emisoras.
    name: Empresas
  - description: Administración del Certificado de Sello Digital (CSD).
    name: Certificados
  - description: >-
      Suscripciones de webhooks: notificaciones de timbrado, cancelación y
      pagos.
    name: Webhooks
paths:
  /cfdi/timbrar-xml:
    post:
      tags:
        - CFDI
      summary: Timbrar un CFDI en XML
      description: >
        Recibe un CFDI 4.0 **sin sellar** — el XML que ya genera tu ERP — y
        devuelve el comprobante timbrado. El reparto de trabajo es: **tú armas
        el documento, Ipsofactura lo sella y lo timbra**. La llave privada del
        CSD está en custodia nuestra, así que el sellado no puede ocurrir de tu
        lado.


        El XML se manda en `xml_base64` (base64 de los bytes UTF-8 del XML; se
        aceptan saltos de línea). Ipsofactura lo valida contra los XSD del SAT,
        lo sella con el CSD del emisor y lo timbra por **el mismo pipeline** que
        `POST /cfdi/timbrar`.


        **El documento es la fuente de la verdad.** Ipsofactura completa lo que
        el XML omite (folio consecutivo por emisor + serie si no trae `Folio`,
        datos del emisor si faltan) pero no sobrescribe lo que sí trae. La
        ventana del SAT (72 h hacia atrás, 5 min hacia adelante) se mide contra
        la `Fecha` **del documento**, no contra la hora en que nos llega: un ERP
        que despacha una cola de ayer tiene que re-fechar el XML.


        **El `TipoDeComprobante` decide la ruta.** `I` y `E` se timbran como
        factura; `P` (con su complemento `pago20:Pagos`) se procesa como
        complemento de pago, resolviendo los CFDI relacionados, la parcialidad y
        el saldo insoluto desde los pagos ya registrados — los valores que el
        documento traiga en esos campos se recalculan.


        **Qué se rechaza con 400:**

        * `Sello`, `NoCertificado` o `Certificado` presentes →
        `UNSUPPORTED_CFDI_CONTENT`. El sellado es nuestro y no re-firmamos un
        documento sellado en otro lado: si tú sellaste el CFDI con tu propio
        CSD, mándalo a `POST /cfdi/timbrar-sellado`.

        * `cfdi:Addenda`, o un `cfdi:Complemento` que no sea Recepción de Pagos
        2.0 (Carta Porte, Comercio Exterior, …) → `UNSUPPORTED_CFDI_CONTENT`.
        Preferimos rechazar antes que entregarte un CFDI legalmente distinto al
        que mandaste.

        * XML mal formado o raíz que no es un `cfdi:Comprobante` 4.0 →
        `INVALID_XML`.

        * Incumple los esquemas del SAT → `XSD_VALIDATION_ERROR`, con hasta tres
        errores (elemento, atributo, línea y columna) en `message`.

        * `xml_base64` no es base64 → `INVALID_BASE64`.


        **Tamaño máximo: 2 MB** de XML decodificado (`REQUEST_TOO_LARGE`, 413).
        Dos órdenes de magnitud arriba del CFDI más grande que hemos visto.


        **`idempotency_key`** (opcional, ≤ 100 caracteres; también se acepta en
        la cabecera `Idempotency-Key`): un reenvío con la misma llave devuelve
        la respuesta del primer intento con la cabecera `Idempotency-Replayed:
        true`, en lugar de timbrar otra vez. La misma llave con un documento
        distinto es 422 `IDEMPOTENCY_KEY_REUSED`: el segundo documento no se
        timbra, y tampoco se te devuelve el CFDI del primero. Sin llave, la
        deduplicación de un `I` o `E` sigue siendo serie + folio + RFC emisor +
        tipo, que responde 409.
      operationId: timbrarXml
      parameters:
        - description: >-
            Alternativa a `idempotency_key` en el cuerpo: reenviar la misma
            llave devuelve la respuesta del primer intento
            (`Idempotency-Replayed: true`) en lugar de timbrar otra vez, y la
            misma llave con un cuerpo distinto es 422 `IDEMPOTENCY_KEY_REUSED`.
            Máximo 100 caracteres ASCII imprimibles, sin espacios. Si además
            envías el campo en el cuerpo, los dos valores deben coincidir; si
            no, la respuesta es 400 `VALIDATION_ERROR`.
          in: header
          name: Idempotency-Key
          schema:
            example: order-4711
            maxLength: 100
            type: string
      requestBody:
        content:
          application/json:
            examples:
              Ingreso PUE en MXN, sin sellar:
                description: >-
                  El `Fecha` del ejemplo es fijo: decodifica el base64, ajústalo
                  a la hora de emisión y vuelve a codificar antes de mandarlo.
                value:
                  idempotency_key: erp-doc-9001
                  xml_base64: >-
                    PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48Y2ZkaTpDb21wcm9iYW50ZSB4bWxuczpjZmRpPSJodHRwOi8vd3d3LnNhdC5nb2IubXgvY2ZkLzQiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3d3dy5zYXQuZ29iLm14L2NmZC80IGh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvNC9jZmR2NDAueHNkIiBWZXJzaW9uPSI0LjAiIFNlcmllPSJBIiBGb2xpbz0iMSIgRmVjaGE9IjIwMjYtMDgtMTFUMTI6MDA6MDAiIEZvcm1hUGFnbz0iMDMiIFN1YlRvdGFsPSIxMDAuMDAiIE1vbmVkYT0iTVhOIiBUb3RhbD0iMTE2LjAwIiBUaXBvRGVDb21wcm9iYW50ZT0iSSIgRXhwb3J0YWNpb249IjAxIiBNZXRvZG9QYWdvPSJQVUUiIEx1Z2FyRXhwZWRpY2lvbj0iMjYwMTUiPjxjZmRpOkVtaXNvciBSZmM9IkVLVTkwMDMxNzNDOSIgTm9tYnJlPSJFU0NVRUxBIEtFTVBFUiBVUkdBVEUgU0EgREUgQ1YiIFJlZ2ltZW5GaXNjYWw9IjYwMSIvPjxjZmRpOlJlY2VwdG9yIFJmYz0iVVJFMTgwNDI5VE02IiBOb21icmU9IlVOSVZFUlNJREFEIFJPQk9USUNBIEVTUEFOT0xBIiBEb21pY2lsaW9GaXNjYWxSZWNlcHRvcj0iNjUwMDAiIFJlZ2ltZW5GaXNjYWxSZWNlcHRvcj0iNjAxIiBVc29DRkRJPSJHMDMiLz48Y2ZkaTpDb25jZXB0b3M+PGNmZGk6Q29uY2VwdG8gQ2xhdmVQcm9kU2Vydj0iMDEwMTAxMDEiIENhbnRpZGFkPSIxIiBDbGF2ZVVuaWRhZD0iRTQ4IiBEZXNjcmlwY2lvbj0iU2VydmljaW8iIFZhbG9yVW5pdGFyaW89IjEwMC4wMCIgSW1wb3J0ZT0iMTAwLjAwIiBPYmpldG9JbXA9IjAyIj48Y2ZkaTpJbXB1ZXN0b3M+PGNmZGk6VHJhc2xhZG9zPjxjZmRpOlRyYXNsYWRvIEJhc2U9IjEwMC4wMCIgSW1wdWVzdG89IjAwMiIgVGlwb0ZhY3Rvcj0iVGFzYSIgVGFzYU9DdW90YT0iMC4xNjAwMDAiIEltcG9ydGU9IjE2LjAwIi8+PC9jZmRpOlRyYXNsYWRvcz48L2NmZGk6SW1wdWVzdG9zPjwvY2ZkaTpDb25jZXB0bz48L2NmZGk6Q29uY2VwdG9zPjxjZmRpOkltcHVlc3RvcyBUb3RhbEltcHVlc3Rvc1RyYXNsYWRhZG9zPSIxNi4wMCI+PGNmZGk6VHJhc2xhZG9zPjxjZmRpOlRyYXNsYWRvIEJhc2U9IjEwMC4wMCIgSW1wdWVzdG89IjAwMiIgVGlwb0ZhY3Rvcj0iVGFzYSIgVGFzYU9DdW90YT0iMC4xNjAwMDAiIEltcG9ydGU9IjE2LjAwIi8+PC9jZmRpOlRyYXNsYWRvcz48L2NmZGk6SW1wdWVzdG9zPjwvY2ZkaTpDb21wcm9iYW50ZT4=
            schema:
              $ref: '#/components/schemas/TimbrarXmlRequest'
        required: true
      responses:
        '201':
          content:
            application/json:
              examples:
                Timbrado:
                  description: Timbrado
                  value:
                    cadena_original_sat: '||1.1|...||'
                    folio: '101'
                    id: 9a4f07ff-3abc-435f-b7a6-88c2bc65b4c1
                    numero_certificado_sat: '00001000000500000000'
                    sello_cfdi: ...
                    sello_sat: ...
                    serie: A
                    stamped_at: '2026-08-28T10:00:00'
                    uuid: 6128396f-c09b-4ec6-8699-43da5a244971
              schema:
                $ref: '#/components/schemas/TimbrarResponse'
          description: CFDI timbrado
        '400':
          content:
            application/json:
              examples:
                INVALID_BASE64:
                  description: INVALID_BASE64
                  value:
                    code: INVALID_BASE64
                    message: >-
                      Field 'xml_base64' is not valid base64: Illegal base64
                      character 3c
                INVALID_XML:
                  description: INVALID_XML
                  value:
                    code: INVALID_XML
                    message: >-
                      The document root is not a 'cfdi:Comprobante' in the CFDI
                      4.0 namespace (http://www.sat.gob.mx/cfd/4)
                UNSUPPORTED_CFDI_CONTENT:
                  description: UNSUPPORTED_CFDI_CONTENT
                  value:
                    code: UNSUPPORTED_CFDI_CONTENT
                    message: >-
                      The comprobante already carries a 'Sello'. Send it
                      unsigned: ipsofactura seals the CFDI with the emisor's CSD
                      before stamping it. To stamp a document you sealed
                      yourself, use POST /cfdi/timbrar-sellado
                VALIDATION_ERROR:
                  description: VALIDATION_ERROR
                  value:
                    code: VALIDATION_ERROR
                    message: >-
                      A comprobante de tipo I requires 'MetodoPago'
                      (metodo_pago)
                XSD_VALIDATION_ERROR:
                  description: XSD_VALIDATION_ERROR
                  value:
                    code: XSD_VALIDATION_ERROR
                    message: >-
                      The CFDI does not satisfy the SAT schema (CFDI 4.0 / Pagos
                      2.0): line 1, column 842: cvc-attribute.3: The value
                      '99.9' of attribute 'TasaOCuota' on element
                      'cfdi:Traslado' is not valid with respect to its type
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            El documento no se puede timbrar. `code` dice qué revisar:
            `INVALID_BASE64`, `INVALID_XML`, `XSD_VALIDATION_ERROR`,
            `UNSUPPORTED_CFDI_CONTENT`, o `VALIDATION_ERROR` para las reglas de
            negocio del comprobante (forma/método de pago, fecha fuera de
            ventana, cálculos, moneda).
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            API key faltante o inválida (AUTHENTICATION_REQUIRED /
            INVALID_API_KEY)
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            La cuenta está suspendida (ACCOUNT_SUSPENDED). Un recurso de otra
            cuenta NO responde 403: responde 404, igual que uno que no existe.
        '404':
          content:
            application/json:
              examples:
                RFC_NOT_FOUND:
                  description: RFC_NOT_FOUND
                  value:
                    code: RFC_NOT_FOUND
                    message: El RFC emisor no está configurado para esta empresa
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: El RFC emisor del documento no está registrado en la cuenta
        '409':
          content:
            application/json:
              examples:
                DUPLICATE_INVOICE:
                  description: DUPLICATE_INVOICE
                  value:
                    code: DUPLICATE_INVOICE
                    message: Ya existe un CFDI timbrado con la misma serie y folio
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            CFDI duplicado (misma serie + folio + RFC emisor + tipo), o una
            petición idéntica con el mismo idempotency_key aún en proceso
        '413':
          content:
            application/json:
              examples:
                REQUEST_TOO_LARGE:
                  description: REQUEST_TOO_LARGE
                  value:
                    code: REQUEST_TOO_LARGE
                    message: >-
                      Field 'xml_base64' exceeds the maximum size of 2 MB for a
                      CFDI document
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            El cuerpo de la petición excede 4 MB, o el XML decodificado excede 2
            MB
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: El idempotency_key ya se usó con un documento distinto
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            Límite de requests por minuto excedido (RATE_LIMIT_EXCEEDED). Ver
            headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
            y Retry-After.
        '503':
          content:
            application/json:
              examples:
                SERVICE_UNAVAILABLE:
                  description: SERVICE_UNAVAILABLE
                  value:
                    code: SERVICE_UNAVAILABLE
                    message: >-
                      Servicio de timbrado temporalmente no disponible. Por
                      favor intenta en unos minutos
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: Servicio de timbrado no disponible
components:
  schemas:
    TimbrarXmlRequest:
      properties:
        idempotency_key:
          description: >-
            Llave de idempotencia opcional (máximo 100 caracteres). Un reenvío
            con la misma llave devuelve la respuesta del primer intento en lugar
            de timbrar otra vez; la misma llave con un documento distinto se
            rechaza con 422. También puede enviarse en la cabecera
            `Idempotency-Key`.
          maxLength: 100
          minLength: 0
          pattern: ^[\x21-\x7E]+$
          type: string
        xml_base64:
          description: >-
            CFDI 4.0 **sin sellar** (nodo `cfdi:Comprobante`), codificado en
            base64 y en UTF-8. Ipsofactura lo valida contra los XSD del SAT, lo
            sella con el CSD del emisor y lo timbra. Máximo 2 MB una vez
            decodificado. No envíe `Sello`, `NoCertificado` ni `Certificado`: el
            sellado es nuestro.
          example: >-
            PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48Y2ZkaTpDb21wcm9iYW50ZSB4bWxuczpjZmRpPSJodHRwOi8vd3d3LnNhdC5nb2IubXgvY2ZkLzQiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL3d3dy5zYXQuZ29iLm14L2NmZC80IGh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvNC9jZmR2NDAueHNkIiBWZXJzaW9uPSI0LjAiIFNlcmllPSJBIiBGb2xpbz0iMSIgRmVjaGE9IjIwMjYtMDgtMTFUMTI6MDA6MDAiIEZvcm1hUGFnbz0iMDMiIFN1YlRvdGFsPSIxMDAuMDAiIE1vbmVkYT0iTVhOIiBUb3RhbD0iMTE2LjAwIiBUaXBvRGVDb21wcm9iYW50ZT0iSSIgRXhwb3J0YWNpb249IjAxIiBNZXRvZG9QYWdvPSJQVUUiIEx1Z2FyRXhwZWRpY2lvbj0iMjYwMTUiPjxjZmRpOkVtaXNvciBSZmM9IkVLVTkwMDMxNzNDOSIgTm9tYnJlPSJFU0NVRUxBIEtFTVBFUiBVUkdBVEUgU0EgREUgQ1YiIFJlZ2ltZW5GaXNjYWw9IjYwMSIvPjxjZmRpOlJlY2VwdG9yIFJmYz0iVVJFMTgwNDI5VE02IiBOb21icmU9IlVOSVZFUlNJREFEIFJPQk9USUNBIEVTUEFOT0xBIiBEb21pY2lsaW9GaXNjYWxSZWNlcHRvcj0iNjUwMDAiIFJlZ2ltZW5GaXNjYWxSZWNlcHRvcj0iNjAxIiBVc29DRkRJPSJHMDMiLz48Y2ZkaTpDb25jZXB0b3M+PGNmZGk6Q29uY2VwdG8gQ2xhdmVQcm9kU2Vydj0iMDEwMTAxMDEiIENhbnRpZGFkPSIxIiBDbGF2ZVVuaWRhZD0iRTQ4IiBEZXNjcmlwY2lvbj0iU2VydmljaW8iIFZhbG9yVW5pdGFyaW89IjEwMC4wMCIgSW1wb3J0ZT0iMTAwLjAwIiBPYmpldG9JbXA9IjAyIj48Y2ZkaTpJbXB1ZXN0b3M+PGNmZGk6VHJhc2xhZG9zPjxjZmRpOlRyYXNsYWRvIEJhc2U9IjEwMC4wMCIgSW1wdWVzdG89IjAwMiIgVGlwb0ZhY3Rvcj0iVGFzYSIgVGFzYU9DdW90YT0iMC4xNjAwMDAiIEltcG9ydGU9IjE2LjAwIi8+PC9jZmRpOlRyYXNsYWRvcz48L2NmZGk6SW1wdWVzdG9zPjwvY2ZkaTpDb25jZXB0bz48L2NmZGk6Q29uY2VwdG9zPjxjZmRpOkltcHVlc3RvcyBUb3RhbEltcHVlc3Rvc1RyYXNsYWRhZG9zPSIxNi4wMCI+PGNmZGk6VHJhc2xhZG9zPjxjZmRpOlRyYXNsYWRvIEJhc2U9IjEwMC4wMCIgSW1wdWVzdG89IjAwMiIgVGlwb0ZhY3Rvcj0iVGFzYSIgVGFzYU9DdW90YT0iMC4xNjAwMDAiIEltcG9ydGU9IjE2LjAwIi8+PC9jZmRpOlRyYXNsYWRvcz48L2NmZGk6SW1wdWVzdG9zPjwvY2ZkaTpDb21wcm9iYW50ZT4=
          maxLength: 2908052
          minLength: 0
          type: string
      required:
        - xml_base64
      type: object
    TimbrarResponse:
      properties:
        cadena_original_sat:
          type: string
        cfdi_relacionados:
          items:
            $ref: '#/components/schemas/CfdiRelacionadosResponse'
          type: array
        fecha_timbrado:
          deprecated: true
          type: string
        folio:
          type: string
        id:
          type: string
        idempotency_key:
          description: >-
            La clave de idempotencia con la que se timbró este CFDI, si se envió
            una.
          type: string
        numero_certificado_sat:
          type: string
        sandbox:
          type: boolean
        sello_cfdi:
          type: string
        sello_sat:
          type: string
        serie:
          type: string
        stamped_at:
          type: string
        uuid:
          type: string
      type: object
    ErrorResponse:
      properties:
        code:
          description: >-
            Código de error de ipsofactura. Estable y cerrado (catálogo
            ErrorCode), pero deliberadamente grueso: TODO rechazo de validación
            del SAT llega como VALIDATION_ERROR. Úsalo para decidir el flujo
            general (reintentar, autenticar de nuevo, corregir el comprobante).
          example: VALIDATION_ERROR
          type: string
        id:
          type: string
        idempotencyKey:
          type: string
        message:
          description: >-
            Descripción legible del error, en texto libre. NO la parsees: cuando
            el rechazo viene del SAT, este texto lo redacta el PAC, cambia entre
            proveedores y puede cambiar sin previo aviso. Para ramificar por
            regla del SAT usa sat_code; para ramificar por tipo de error usa
            code. El mensaje es para mostrarlo o registrarlo.
          example: >-
            CFDI40147: El RFC del receptor no se encuentra en la lista de
            contribuyentes inscritos no cancelados del SAT
          type: string
        retryAfter:
          format: int32
          type: integer
        sat_code:
          description: >-
            Código de validación del SAT (CFDIxxxxx) del rechazo. Presente SÓLO
            cuando el comprobante fue rechazado por el SAT a través de un PAC;
            ausente en errores propios de ipsofactura (validación local,
            autenticación, rate limit, servicio no disponible). Es específico
            donde `code` es genérico: `code` te dice que hubo un rechazo de
            validación, `sat_code` te dice cuál regla se incumplió. También se
            omite si el PAC no devolvió ningún código: nunca se inventa uno.
          example: CFDI40147
          type: string
    CfdiRelacionadosResponse:
      description: >-
        Grupo de CFDI relacionados: el tipo de relación y los folios fiscales
        relacionados.
      properties:
        tipo_relacion:
          description: >-
            Tipo de relación (c_TipoRelacion), p. ej. 01 para una nota de
            crédito.
          type: string
        uuids:
          description: Folios fiscales (UUID) relacionados con este tipo de relación.
          items:
            type: string
          type: array
      type: object
  securitySchemes:
    apiKeyAuth:
      description: API key emitida por Ipsofactura, enviada en el header x-api.
      in: header
      name: x-api
      type: apiKey

````