Factura global de tickets

Los tickets de Auto CFDI que tus clientes no facturan van a una factura global que emites con Crear factura, la misma petición de cualquier factura global. Lo único propio es el campo ticketId en cada concepto: con él, Fiscalapi verifica que el concepto refleje el ticket y lo marca como facturado en la global.

Una factura global sin ticketId en sus conceptos no marca ningún ticket: siguen en pending y tus clientes los pueden autofacturar, lo que factura dos veces la misma venta. Incluye el ticketId de cada ticket que ampara la global.

Armar la factura global

Arma una global por cada combinación de persona emisora, moneda, tipo de cambio y lugar de expedición: el CFDI lleva un solo valor de cada uno, y el código postal del receptor público en general es el lugar de expedición. Una factura admite como máximo 1,500 conceptos; si tus tickets necesitan más, repártelos en varias globales sin partir un ticket entre dos.

1. Consulta los tickets pendientes

Consulta los tickets en pending del emisor en el periodo con Listar tickets y details=true, que trae los conceptos con sus impuestos.

La lista es paginada: repite la consulta con el siguiente pageNumber mientras hasNextPage sea true.

GET /api/v4/tickets?status=pending&personId=<id>&from=2026-10-01&to=2026-10-31&details=true&pageNumber=1&pageSize=50

2. Arma un concepto por ticket y grupo de impuestos

Cada ticket se convierte en un concepto por cada grupo de sus conceptos con el mismo objeto de impuesto y los mismos impuestos. Fiscalapi calcula todos los impuestos de un concepto sobre la misma base, así que un ticket con productos al 16 % y al 0 % de IVA lleva dos conceptos, ambos con el mismo ticketId.

Campo del conceptoValor
ticketIdID del ticket.
itemCode01010101
itemSkuFolio del ticket.
quantity1
unitOfMeasurementCodeACT
descriptionVenta
unitPriceSuma de la cantidad por el valor unitario de los conceptos del grupo.
discountSuma de los descuentos de los conceptos del grupo.
taxObjectCode e itemTaxesLos del grupo, sin cambios.

Fiscalapi compara, por cada ticket, la suma de sus conceptos en la global contra el ticket guardado: importes, descuentos e impuestos por impuesto, tipo de factor y tasa. Si no coinciden, responde 400 en el ticketId del primer concepto del ticket, sin cobrar.

3. Completa el comprobante

La global de tickets lleva el emisor por referencia (issuer.id), la persona emisora de los tickets. El receptor es el público en general y la forma de pago es la del ticket de mayor monto, según la guía de llenado de la factura global del SAT.

CampoValor
typeCodeI
paymentMethodCodePUE
exportCode01
paymentFormCodeLa forma de pago del ticket de mayor monto.
currencyCode y exchangeRateLos de los tickets. Con MXN, el tipo de cambio es 1.
expeditionZipCodeEl de los tickets.
issuer.idID de la persona emisora de los tickets.
recipientRFC XAXX010101000, PUBLICO EN GENERAL, régimen 616, uso S01 y el código postal de expedición.
globalInformationPeriodicidad, mes y año del periodo. Ver Información global.

POST/api/v4/invoices

Crear factura global de tickets

Este endpoint timbra la factura global con la petición estándar de Crear factura. Con ticketId, Fiscalapi reserva los tickets antes de cobrar el timbre: todos o ninguno. Al guardar el CFDI, los tickets pasan a globalized con el ID de la factura en globalInvoiceId. Si el PAC rechaza el CFDI, los tickets vuelven a su estado anterior y el timbre se reembolsa.

El ejemplo ampara el ticket F-1002, una venta de 166.00 con un refresco de 100.00 al 16 % de IVA y agua natural de 50.00 al 0 %. Sus dos grupos de impuestos van en dos conceptos con el mismo ticketId, y cada concepto suma lo que el ticket guardó en su grupo.

Reglas de ticketId

El campo items[].ticketId es opcional. Sin él, la factura se emite igual que siempre.

ReglaQué pasa si no se cumple
Solo va en una factura global de ingreso, con typeCode I y globalInformation.400 en el ticketId del concepto.
La global lleva el emisor por referencia.400 en Issuer.Id.
El ticket es de tu cuenta, de la misma persona emisora y en la misma moneda que la global.400 en el ticketId del concepto.
Los conceptos del ticket reflejan el ticket guardado.400 en el ticketId del primer concepto del ticket.
El ticket está en pending, o en globalized en la global que sustituyes.409 con data.unavailableTickets, sin cobrar ni tomar tickets.

Reintentos

Un 400 o un 409 no cobran el timbre ni dejan tickets tomados. Ante un 409, data.unavailableTickets trae el id y el estado de cada ticket que ya no estaba disponible: vuelve a consultar los tickets pendientes y arma la global otra vez. No reintentes la misma petición sin cambios.

Si el PAC no responde o responde un error de su servidor sin código de rechazo, el resultado es incierto porque el CFDI pudo timbrarse. Fiscalapi responde el error inesperado y conserva el timbre, el folio y los tickets tomados para revisarlos. No emitas otra global con esos tickets: consulta tus facturas y los tickets, y si siguen en globalizing, escribe a soporte con el traceIdentifier de la respuesta.

Errores

Las fallas llegan como falla de validación, con el campo en PascalCase; por ejemplo, Items[0].TicketId. En los mensajes, i es la posición del concepto, desde 0.

EstadoCuándo ocurreMensaje
400Un concepto trae ticketId y la factura no es una global de ingreso.'items[i].ticketId' solo aplica en una factura global de ingreso: 'typeCode' I con 'globalInformation'. Quita el ticketId o emite la factura global.
400El ticketId va vacío o pasa de 36 caracteres.'items[i].ticketId' debe tener de 1 a 36 caracteres: el id del ticket.
400La global con tickets lleva el emisor por valores. Llega en Issuer.Id.La factura global con tickets requiere el emisor por referencia ('issuer.id'): la persona emisora de los tickets.
400El ticket no existe o es de otra cuenta.'items[i].ticketId' no es un ticket de tu organización.
400El ticket es de otra persona emisora.'items[i].ticketId' es un ticket de otra persona emisora: la factura global lleva solo tickets de su emisor, indicado por referencia ('issuer.id').
400El ticket está en otra moneda.'items[i].ticketId' es un ticket en otra moneda: la factura global lleva solo tickets de su moneda ('currencyCode').
400Los conceptos del ticket no reflejan el ticket guardado.Los conceptos con el ticket de 'items[i].ticketId' (<conceptos>) no reflejan ese ticket: la suma de sus importes (quantity × unitPrice), la de sus descuentos y la de sus impuestos por impuesto, tipo de factor y tasa deben ser las de los conceptos del ticket. Un ticket con impuestos distintos va en un concepto por cada grupo de impuestos, todos con el mismo ticketId.
409Un ticket ya no está disponible. Los tickets y su estado van en data.unavailableTickets.Uno o más tickets ya no están disponibles para la factura global (solo entra un ticket en estado pending): 'data.unavailableTickets' dice cuáles y su estado. No se cobró el timbre ni quedó tomado ningún ticket.

En el mensaje de conceptos que no reflejan el ticket, <conceptos> es la lista de los conceptos con ese ticket; por ejemplo, items[0], items[1]. Los demás errores de la factura son los de Crear factura global.

Request

POST
/api/v4/invoices
curl --location 'https://test.fiscalapi.com/api/v4/invoices' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--data-raw '{
  "versionCode": "4.0",
  "series": "G",
  "date": "2026-11-01T10:00:00",
  "paymentFormCode": "01",
  "paymentMethodCode": "PUE",
  "currencyCode": "MXN",
  "exchangeRate": 1,
  "typeCode": "I",
  "expeditionZipCode": "42501",
  "exportCode": "01",
  "globalInformation": {
    "periodicityCode": "04",
    "monthCode": "10",
    "year": 2026
  },
  "issuer": { "id": "<person_id>" },
  "recipient": {
    "tin": "XAXX010101000",
    "legalName": "PUBLICO EN GENERAL",
    "zipCode": "42501",
    "taxRegimeCode": "616",
    "cfdiUseCode": "S01"
  },
  "items": [
    {
      "ticketId": "<ticket_id>",
      "itemCode": "01010101",
      "itemSku": "F-1002",
      "quantity": 1,
      "unitOfMeasurementCode": "ACT",
      "description": "Venta",
      "unitPrice": 100.000000,
      "discount": 0,
      "taxObjectCode": "02",
      "itemTaxes": [
        { "taxCode": "002", "taxTypeCode": "Tasa", "taxRate": "0.160000", "taxFlagCode": "T" }
      ]
    },
    {
      "ticketId": "<ticket_id>",
      "itemCode": "01010101",
      "itemSku": "F-1002",
      "quantity": 1,
      "unitOfMeasurementCode": "ACT",
      "description": "Venta",
      "unitPrice": 50.000000,
      "discount": 0,
      "taxObjectCode": "02",
      "itemTaxes": [
        { "taxCode": "002", "taxTypeCode": "Tasa", "taxRate": "0.000000", "taxFlagCode": "T" }
      ]
    }
  ]
}'

Response 409

{
    "data": {
        "unavailableTickets": [
            { "ticketId": "9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f", "status": "invoiced" }
        ]
    },
    "succeeded": false,
    "message": "Sorry, it's not me, it's you.",
    "details": "Uno o más tickets ya no están disponibles para la factura global (solo entra un ticket en estado pending): 'data.unavailableTickets' dice cuáles y su estado. No se cobró el timbre ni quedó tomado ningún ticket.",
    "httpStatusCode": 409
}

Sustituir una factura global de tickets

Para excluir tickets de una global ya timbrada, por ejemplo porque un cliente pidió su factura después, sustitúyela con los endpoints estándar: emite una nueva global con los tickets que se quedan y cancela la original. La guía de llenado de la factura global del SAT describe este caso: la nueva global sin la operación que se factura de forma nominativa, relacionada con la original por sustitución.

 Global original (vigente)            Tickets A, B y C en globalized
            |
            |  1. POST /api/v4/invoices
            |     relatedInvoices: tipo 04 con el UUID de la original
            |     items[].ticketId: solo A y B
            v
 Global sustituta (vigente)           A y B pasan a la sustituta (globalized)
                                      C sigue en globalized en la original
            |
            |  2. DELETE /api/v4/invoices
            |     id de la original, motivo 01 y el UUID de la sustituta
            v
 Original en Cancelando --> el SAT confirma la cancelación
            |
            v
 Original cancelada                   C vuelve a pending

Cada paso es una petición que puedes reintentar, y el estado lo dan los tickets y las facturas:

PasoPeticiónQué cambia
1Crear factura con relatedInvoices de tipo 04 y el UUID de la original, y el ticketId de los tickets que se quedan.Al guardar la sustituta, sus tickets pasan de la original a ella.
2Cancelar factura de la original con motivo 01 y la sustituta en replacementUuid.La original pasa a Cancelando hasta que el SAT confirma.
3Ninguna: Fiscalapi consulta al SAT.Confirmada la cancelación, los tickets excluidos vuelven a pending.

Para armar la sustituta, consulta los tickets de la original con Listar tickets y globalInvoiceId con el ID de la original. Copia de la original la serie, la persona emisora, la información global, la moneda, el tipo de cambio y el lugar de expedición, y vuelve a calcular la forma de pago con los tickets que se quedan.

Mientras la original siga vigente, sus tickets excluidos siguen en globalized y el portal no los autofactura. Cuando se confirma la cancelación de una global, cada uno de sus tickets vuelve a la global vigente más reciente que lo amparaba antes, o a pending si no hay ninguna. Por ejemplo, si cancelas la sustituta mientras la original sigue vigente, sus tickets vuelven a la original. Así, una venta nunca queda en pending mientras un CFDI vigente la ampara.

Si la original solo amparaba tickets que excluyes, no hay sustituta: cancélala con el motivo que corresponda a tu caso, sin relación. Consulta los motivos en Cancelar factura.

¿Le resultó útil esta página?