> ## 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 ya sellado

> Recibe un CFDI 4.0 que **tú ya sellaste con tu propio CSD** y lo timbra ante el SAT. El reparto de trabajo se invierte respecto de `POST /cfdi/timbrar-xml`: aquí **tú armas y sellas el documento**, Ipsofactura sólo verifica el sello y lo lleva al PAC.

Es la ruta para quien **no quiere entregar su llave privada en custodia**: la empresa emisora no necesita tener ningún CSD cargado en Ipsofactura. El certificado viaja dentro del propio comprobante.

El XML se manda en `xml_cfdi_base64` (base64 de los bytes UTF-8 del XML; se aceptan saltos de línea). Debe traer `Sello`, `Certificado` y `NoCertificado`.

**Qué se verifica antes de timbrar** (todo falla con 400, nunca con un timbrado a medias):
* El documento cumple los XSD del SAT (`XSD_VALIDATION_ERROR`).
* El `Certificado` embebido es de tu RFC emisor, está vigente y su número de serie coincide con `NoCertificado` (`SEAL_VERIFICATION_FAILED`).
* El `Sello` verifica contra la cadena original recalculada **de los bytes que nos mandaste** (`SEAL_VERIFICATION_FAILED`). Sella exactamente lo que envías: cualquier cambio posterior a la firma la invalida.

**El documento se timbra tal cual.** No se le toca un solo atributo, así que Ipsofactura **no asigna folio consecutivo** en esta ruta: `Serie` y `Folio` son los que traiga el documento (ambos son opcionales en el esquema del SAT; si no vienen, se timbra sin ellos). Escribir un `Folio` en un comprobante ya firmado rompería su sello. Si quieres el folio consecutivo de Ipsofactura, usa `POST /cfdi/timbrar-xml`.

**Sin `Serie` ni `Folio` el `Sello` es la identidad.** Un comprobante sellado que no trae ninguno de los dos no tiene identidad fiscal con la que deduplicarlo, así que Ipsofactura la deriva de su propio `Sello` — que firma exactamente esos bytes. Reenviar el mismo documento devuelve el CFDI ya timbrado (201) en vez de timbrar otro; un reenvío simultáneo recibe 409 mientras el primero está en curso. Es la misma mecánica que `POST /cfdi/timbrar` aplica cuando deriva la llave de serie + folio: la llave derivada es interna y no se devuelve en la respuesta.

**Sólo tipos `I` y `E`.** Un complemento de pago (`P`) se rechaza: la parcialidad y el saldo insoluto se derivan de los pagos ya registrados, y recalcularlos invalidaría tu sello.

**Requiere un PAC que acepte comprobantes pre-sellados.** Si en ese momento no hay ninguno disponible, la respuesta es 503 — nunca un timbrado re-sellado por nuestro lado.

**Tamaño máximo: 2 MB** de XML decodificado (`REQUEST_TOO_LARGE`, 413).

**`idempotency_key`** (opcional, ≤ 100 caracteres): un reenvío con la misma llave devuelve la respuesta del primer intento con la cabecera `Idempotency-Replayed: true`.




## OpenAPI

````yaml /openapi.json post /cfdi/timbrar-sellado
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-sellado:
    post:
      tags:
        - CFDI
      summary: Timbrar un CFDI ya sellado
      description: >
        Recibe un CFDI 4.0 que **tú ya sellaste con tu propio CSD** y lo timbra
        ante el SAT. El reparto de trabajo se invierte respecto de `POST
        /cfdi/timbrar-xml`: aquí **tú armas y sellas el documento**, Ipsofactura
        sólo verifica el sello y lo lleva al PAC.


        Es la ruta para quien **no quiere entregar su llave privada en
        custodia**: la empresa emisora no necesita tener ningún CSD cargado en
        Ipsofactura. El certificado viaja dentro del propio comprobante.


        El XML se manda en `xml_cfdi_base64` (base64 de los bytes UTF-8 del XML;
        se aceptan saltos de línea). Debe traer `Sello`, `Certificado` y
        `NoCertificado`.


        **Qué se verifica antes de timbrar** (todo falla con 400, nunca con un
        timbrado a medias):

        * El documento cumple los XSD del SAT (`XSD_VALIDATION_ERROR`).

        * El `Certificado` embebido es de tu RFC emisor, está vigente y su
        número de serie coincide con `NoCertificado`
        (`SEAL_VERIFICATION_FAILED`).

        * El `Sello` verifica contra la cadena original recalculada **de los
        bytes que nos mandaste** (`SEAL_VERIFICATION_FAILED`). Sella exactamente
        lo que envías: cualquier cambio posterior a la firma la invalida.


        **El documento se timbra tal cual.** No se le toca un solo atributo, así
        que Ipsofactura **no asigna folio consecutivo** en esta ruta: `Serie` y
        `Folio` son los que traiga el documento (ambos son opcionales en el
        esquema del SAT; si no vienen, se timbra sin ellos). Escribir un `Folio`
        en un comprobante ya firmado rompería su sello. Si quieres el folio
        consecutivo de Ipsofactura, usa `POST /cfdi/timbrar-xml`.


        **Sin `Serie` ni `Folio` el `Sello` es la identidad.** Un comprobante
        sellado que no trae ninguno de los dos no tiene identidad fiscal con la
        que deduplicarlo, así que Ipsofactura la deriva de su propio `Sello` —
        que firma exactamente esos bytes. Reenviar el mismo documento devuelve
        el CFDI ya timbrado (201) en vez de timbrar otro; un reenvío simultáneo
        recibe 409 mientras el primero está en curso. Es la misma mecánica que
        `POST /cfdi/timbrar` aplica cuando deriva la llave de serie + folio: la
        llave derivada es interna y no se devuelve en la respuesta.


        **Sólo tipos `I` y `E`.** Un complemento de pago (`P`) se rechaza: la
        parcialidad y el saldo insoluto se derivan de los pagos ya registrados,
        y recalcularlos invalidaría tu sello.


        **Requiere un PAC que acepte comprobantes pre-sellados.** Si en ese
        momento no hay ninguno disponible, la respuesta es 503 — nunca un
        timbrado re-sellado por nuestro lado.


        **Tamaño máximo: 2 MB** de XML decodificado (`REQUEST_TOO_LARGE`, 413).


        **`idempotency_key`** (opcional, ≤ 100 caracteres): un reenvío con la
        misma llave devuelve la respuesta del primer intento con la cabecera
        `Idempotency-Replayed: true`.
      operationId: timbrarSellado
      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:
            schema:
              $ref: '#/components/schemas/TimbrarSelladoRequest'
        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:
                SEAL_VERIFICATION_FAILED:
                  description: SEAL_VERIFICATION_FAILED
                  value:
                    code: SEAL_VERIFICATION_FAILED
                    message: >-
                      The 'Sello' does not verify against the cadena original of
                      the document you sent. Seal the exact bytes you send us:
                      any change made after signing invalidates the seal
                UNSUPPORTED_CFDI_CONTENT:
                  description: UNSUPPORTED_CFDI_CONTENT
                  value:
                    code: UNSUPPORTED_CFDI_CONTENT
                    message: >-
                      The comprobante carries no 'Sello'. This endpoint stamps a
                      CFDI you sealed with your own CSD; to have ipsofactura
                      seal it, send it to POST /cfdi/timbrar-xml
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            El documento no se puede timbrar. `code` dice qué revisar:
            `INVALID_BASE64`, `INVALID_XML`, `XSD_VALIDATION_ERROR`,
            `SEAL_VERIFICATION_FAILED` (el sello o el certificado del documento
            no se sostienen), `UNSUPPORTED_CFDI_CONTENT` (viene sin sellar, o es
            un tipo `P`), o `VALIDATION_ERROR` para las reglas de negocio del
            comprobante.
        '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:
              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:
              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 payload 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: >-
                      No stamping provider that accepts a pre-sealed CFDI is
                      available right now. Retry later, or send the comprobante
                      unsigned to POST /cfdi/timbrar-xml
              schema:
                $ref: '#/components/schemas/ErrorResponse'
          description: >-
            Servicio de timbrado no disponible, o ningún PAC disponible acepta
            comprobantes pre-sellados
components:
  schemas:
    TimbrarSelladoRequest:
      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.
          maxLength: 100
          minLength: 0
          pattern: ^[\x21-\x7E]+$
          type: string
        xml_cfdi_base64:
          description: >-
            CFDI 4.0 **ya sellado con tu propio CSD** (nodo `cfdi:Comprobante`),
            codificado en base64 y en UTF-8. Debe traer `Sello`, `Certificado` y
            `NoCertificado`: Ipsofactura no sella este documento, sólo verifica
            el sello y lo timbra tal cual. Máximo 2 MB una vez decodificado.
          maxLength: 2908052
          minLength: 0
          type: string
      required:
        - xml_cfdi_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

````