Tickets
A ticket (a sales receipt your customer can later turn into a CFDI) is a paid sale that your point of sale records in Fiscalapi for Auto CFDI invoicing (CFDI: Mexico's electronic invoice). When you record it, you receive a code and its QR code URL, which you print on the sales receipt: your customer uses them to generate their invoice in the auto-invoice portal (auto-invoice: a CFDI your customer requests from a ticket; you remain the issuer). You include any sale your customers don't invoice in the global invoice for tickets (global invoice: factura global, one CFDI that groups a period's sales to the general public).
Recording a ticket requires Auto CFDI invoicing in your subscription and uses 1 ticket credit from the authenticated person (any individual or legal entity registered in your tenant), even if the ticket is never invoiced. Viewing your tickets doesn't require Auto CFDI invoicing: if you remove the add-on (an extra you add to your subscription), you still see the ones you recorded.
Each ticket belongs to an issuing person in your tenant (the space in Fiscalapi that holds your issuers, recipients, credits, and data) and stores the sale's line items and taxes with the same names as Create an invoice. Fiscalapi validates its structure, the SAT catalogs (SAT: Mexico's tax authority, Servicio de Administración Tributaria), and that the total matches to the cent. The taxes you declare are your responsibility.
Ticket statuses
A ticket's status comes as camelCase text in the status field. Only a ticket in pending status can be auto-invoiced, included in a global invoice, or canceled.
| Status | What it means |
|---|---|
pending | Not invoiced: your customer can auto-invoice it, or you can include it in a global invoice. |
invoicing | Your customer is stamping it (stamping: the certification by a SAT-authorized provider that makes a CFDI valid) in the portal. |
invoiced | It has its own individual invoice, whose ID is in invoiceId. |
globalizing | A global invoice that includes it is being stamped. |
globalized | It's included in a global invoice, whose ID is in globalInvoiceId. |
cancelled | You canceled it before it was invoiced. This status is final and releases its series and folio number (the issuer's own sequential number for the ticket). |
New statuses may appear: treat an unknown value as a ticket you can't invoice yet.
Ticket model
The ticket model (Ticket) is the one that List tickets and Get a ticket by ID return. The line items come only with details=true, and the history only when you get a single ticket.
Properties
- Name
id- Type
- string?
- Description
The ticket ID, assigned by Fiscalapi.
- Name
personId- Type
- string?
- Description
The ID of the ticket's issuing person.
- Name
folio- Type
- string?
- Description
The point-of-sale folio number. It isn't the CFDI folio number: in the global invoice, it goes in
itemSku(NoIdentificacion).
- Name
series- Type
- string?
- Description
The point-of-sale series, or
nullif the ticket has no series.
- Name
paymentDate- Type
- string?
- Description
The payment date and time in Mexico local time, with no time zone, exactly as the point of sale sent it.
- Name
paymentFormCode- Type
- string?
- Description
The form of payment (how the customer paid: cash, bank transfer, card; the
FormaPagokey). SAT catalogc_FormaPago.
- Name
currencyCode- Type
- string?
- Description
The sale's currency. SAT catalog
c_Moneda.
- Name
exchangeRate- Type
- number | decimal?
- Description
The sale's exchange rate, or
nullfor a sale in MXN.
- Name
expeditionZipCode- Type
- string?
- Description
The postal code of the place of issue (the postal code where the CFDI is issued).
- Name
subtotal- Type
- number | decimal?
- Description
The subtotal calculated by Fiscalapi.
- Name
totalTransferredTaxes- Type
- number | decimal?
- Description
The total of transferred taxes (traslado, a tax the issuer charges the recipient) calculated by Fiscalapi.
- Name
totalWithheldTaxes- Type
- number | decimal?
- Description
The total withholding (retención) calculated by Fiscalapi.
- Name
total- Type
- number | decimal?
- Description
The sale's total. It matches the calculated total to the cent.
- Name
status- Type
- string?
- expandable
- Description
The ticket status. See Ticket statuses.
- Type
- enum:
- Values
- "pending""invoicing""invoiced"
- Name
selfInvoiceEligible- Type
- boolean?
- Description
trueif your customer can auto-invoice it today: the ticket is inpendingand its auto-invoice window is still open. It's calculated on each query, with the current settings.
- Name
eligibleUntil- Type
- string?
- Description
The last moment of the auto-invoice window, or
nullif the window doesn't close. It's calculated on each query.
- Name
invoiceId- Type
- string?
- Description
The ID of the ticket's individual invoice, or
null.
- Name
globalInvoiceId- Type
- string?
- Description
The ID of the global invoice that covers the ticket, or
null.
- Name
createdAt- Type
- string?
- Description
The date and time the ticket was recorded.
- Name
items- Type
- array of objects (TicketItem)?
The sale's line items with their taxes. Only with
details=true.- Name
itemCode- Type
- string?
- Description
The SAT product or service key. SAT catalog
c_ClaveProdServ.
- Name
itemSku- Type
- string?
- Description
The product's identifier in your point of sale.
- Name
quantity- Type
- number | decimal?
- Description
The quantity sold.
- Name
unitOfMeasurementCode- Type
- string?
- Description
The unit of measure. SAT catalog
c_ClaveUnidad.
- Name
description- Type
- string?
- Description
The product or service description.
- Name
unitPrice- Type
- number | decimal?
- Description
The unit price before taxes.
- Name
discount- Type
- number | decimal?
- Description
The line item's discount.
- Name
taxObjectCode- Type
- string?
- Description
The tax object (whether an item is subject to tax,
ObjetoImp). SAT catalogc_ObjetoImp.
- Name
itemTaxes- Type
- array of objects (TicketItemTax)?
The line item's taxes.
- Name
taxCode- Type
- string?
- Description
The tax. SAT catalog
c_Impuesto.
- Name
taxTypeCode- Type
- string?
- Description
The factor type:
Tasa(rate),Cuota(fixed amount), orExento(exempt).
- Name
taxRate- Type
- number | decimal?
- Description
The tax rate or fixed amount.
- Name
taxFlagCode- Type
- string?
- Description
Transferred tax (T) or withholding (R).
- Name
events- Type
- array of objects (TicketEvent)?
The ticket's history, in order. Only in Get a ticket by ID with
details=true.- Name
type- Type
- string?
- Description
What happened; for example,
ingested,invoiced, orcancelled. New types may appear.
- Name
occurredAt- Type
- string?
- Description
The date and time of the event.
- Name
actorType- Type
- string?
- Description
Who caused it:
tenant(your tenant),portal(your customer in the portal), orsystem(a Fiscalapi process).
- Name
invoiceId- Type
- string?
- Description
The ID of the event's invoice, or
null.
Create a ticket
This endpoint records a paid sale and uses 1 ticket credit from the authenticated person. It returns 201 with the ticket code and its QR code URL. The CFDI issued for the ticket is version 4.0, an income invoice (type I, the regular sales invoice), and not an export.
Its payment method (whether the CFDI is paid in full when issued, PUE, or later or in installments, PPD; not how the customer pays) is PUE (paid in full when the CFDI is issued, Pago en una sola exhibición).
Before charging, Fiscalapi checks that the issuing person belongs to your tenant and has complete tax details, a current CSD (Certificado de Sello Digital, the digital seal certificate used to sign CFDIs), and stamps (stamp: one credit, used each time you stamp a CFDI), because each auto-invoice uses one stamp from their balance. It also checks that the payment date is no more than 5 minutes in the future and that the total matches the calculated total to the cent.
Idempotency-Key header
The Idempotency-Key header is optional and accepts up to 64 characters. Use it to retry safely: if you repeat the same idempotency key with the same body, the endpoint returns 200 with the original ticket and doesn't charge again. The same key with a different body returns 422. Use a new key for each sale.
Without a key, the issuer can have only one active ticket with the same series and folio number: a duplicate returns 409, without charging, with the ticket that already has them in data.existingTicketId. To record that folio number again, cancel the existing ticket and record it again, which uses another credit.
Request body
- Name
issuer- Type
- object (TicketIssuer)
- required
The issuing person, by reference.
- Name
id- Type
- string
- required
- Description
The ID of the issuing person in your tenant that invoices the ticket.
- Name
folio- Type
- string
- required
- Description
The point-of-sale folio number, 1 to 40 characters, without the vertical bar character.
- Name
series- Type
- string?
- Description
The point-of-sale series, 1 to 25 characters, without the vertical bar character. If you omit it, the ticket has no series.
- Name
paymentDate- Type
- string
- required
- Description
The payment date and time in yyyy-MM-ddTHH:mm:ss format, in Mexico local time. This field doesn't use the
X-TIME-ZONEheader.
- Name
paymentFormCode- Type
- string
- required
- Description
The form of payment used to settle the sale. SAT catalog
c_FormaPago. It doesn't accept"99"(por definir, to be defined).
- Name
currencyCode- Type
- string?
- Description
The sale's currency. SAT catalog
c_Moneda. It doesn't accept"XXX".Default:MXN
- Name
exchangeRate- Type
- number | decimal?
- Description
The sale's exchange rate. With MXN, omit it or send
null; with another currency, it's required and greater than 0.
- Name
expeditionZipCode- Type
- string?
- Description
The place-of-issue postal code, 5 digits. If you omit it, Fiscalapi uses the issuing person's.
- Name
total- Type
- number | decimal
- required
- Description
The total the point of sale charged. It must match, to the cent, the total calculated from the line items and taxes.
- Name
items- Type
- array of objects (TicketItem)
- required
The sale's line items, from 1 to 1,500.
- Name
itemCode- Type
- string
- required
- Description
The SAT product or service key, 8 characters. SAT catalog
c_ClaveProdServ.
- Name
itemSku- Type
- string
- required
- Description
The product's identifier in your point of sale.
- Name
quantity- Type
- number | decimal
- required
- Description
The quantity sold, greater than 0 and with up to 6 decimal places.
- Name
unitOfMeasurementCode- Type
- string
- required
- Description
The unit of measure. SAT catalog
c_ClaveUnidad.
- Name
description- Type
- string
- required
- Description
The description, 1 to 1,000 characters, without the vertical bar character.
- Name
unitPrice- Type
- number | decimal
- required
- Description
The unit price before taxes, with up to 6 decimal places.
- Name
discount- Type
- number | decimal?
- Description
The line item's discount, with up to 6 decimal places.
Default:0
- Name
taxObjectCode- Type
- string
- required
- Description
The tax object. SAT catalog
c_ObjetoImp. With 02, the line item carries at least one tax; with 01, 03, 04, or 05, it carries no taxes.
- Name
itemTaxes- Type
- array of objects (TicketItemTax)?
The line item's taxes, with the same fields as an invoice's. If the line item has no taxes, omit it or send an empty list.
- Name
taxCode- Type
- string
- required
- Description
The tax. SAT catalog
c_Impuesto.
- Name
taxTypeCode- Type
- string
- required
- Description
The factor type:
Tasa(rate),Cuota(fixed amount), orExento(exempt).
- Name
taxRate- Type
- number | decimal
- required
- Description
The tax rate or fixed amount; for example,
"0.160000".
- Name
taxFlagCode- Type
- string
- required
- Description
Transferred tax (T) or withholding (R).
Response
The body is the same in the 201 for a new ticket and in the 200 for a repeat with the same key.
- Name
id- Type
- string?
- Description
The ticket ID.
- Name
ticketCode- Type
- string?
- Description
The ticket code without hyphens, 12 characters. Print it in groups of 4 separated by hyphens; the portal accepts both forms.
- Name
qrUrl- Type
- string?
- Description
The portal URL that opens the ticket. Print it as a QR code on the sales receipt. See Code and QR code.
- Name
status- Type
- string?
- Description
The ticket status; when you record it,
pending.
- Name
selfInvoiceEligible- Type
- boolean?
- Description
trueif your customer can auto-invoice it today.
- Name
eligibleUntil- Type
- string?
- Description
The last moment of the auto-invoice window, or
nullif the window doesn't close.
- Name
subtotal- Type
- number | decimal?
- Description
The calculated subtotal.
- Name
totalTransferredTaxes- Type
- number | decimal?
- Description
The calculated total of transferred taxes.
- Name
totalWithheldTaxes- Type
- number | decimal?
- Description
The calculated total withholding.
- Name
total- Type
- number | decimal?
- Description
The calculated total, equal to the one you sent.
Errors
Validation failures arrive in ValidationFailure with the field that failed, before any charge. In the messages, <dato> is the tax detail the issuing person is missing, n is the available balance, and m is the required amount.
| Status | When it happens | Message |
|---|---|---|
| 400 | The issuer or its id is missing. | 'issuer.id' es obligatorio: la persona emisora del tenant que facturará el ticket. |
| 400 | The folio number is missing or doesn't match its format. | 'folio' es obligatorio y debe tener de 1 a 40 caracteres sin el carácter '|'. |
| 400 | The series doesn't match its format. | 'series' debe tener de 1 a 25 caracteres sin el carácter '|'; si se omite, el ticket queda sin serie. |
| 400 | The form of payment is 99. | 'paymentFormCode' no puede ser 99 (por definir): un ticket es una venta ya pagada; usa la forma con la que se pagó. |
| 400 | The currency is XXX. | 'currencyCode' no puede ser XXX: un ticket es una venta con importes. |
| 400 | There's an exchange rate with MXN. | 'exchangeRate' no aplica con la moneda MXN (también es MXN si se omite 'currencyCode'): omítelo o envíalo null. |
| 400 | The exchange rate is missing with another currency, or isn't greater than 0. | 'exchangeRate' es obligatorio con una moneda distinta de MXN y debe ser mayor que 0. |
| 400 | There are no line items. | 'items' debe tener al menos un concepto. |
| 400 | There are more than 1,500 line items. | Un ticket admite como máximo 1500 conceptos (items). |
| 400 | A line item with tax object (whether an item is subject to tax, ObjetoImp) 02 has no taxes. | 'itemTaxes' es obligatorio con taxObjectCode 02 (sí objeto de impuesto): agrega al menos un impuesto. |
| 400 | A line item with tax object 01, 03, 04, or 05 has taxes. | 'itemTaxes' debe ir vacío con taxObjectCode 01, 03, 04 o 05: esos conceptos no desglosan impuestos. |
| 400 | The Idempotency-Key header is longer than 64 characters. | El header Idempotency-Key admite como máximo 64 caracteres. |
| 403 | The authenticated person doesn't have enough ticket credits. | Saldo insuficiente. Disponible: n, Requerido: m |
| 403 | Your subscription doesn't include Auto CFDI invoicing. See Permissions and subscription. | Para ingerir tickets tu suscripción debe incluir: Auto CFDI. |
| 409 | The issuer already has an active ticket with that series and folio number. Its id is in data.existingTicketId. | Ya hay un ticket activo con esa serie y folio para el emisor: 'data.existingTicketId' es el ticket que lo tiene. Para volver a ingerirlo, cancela ese ticket primero. |
| 409 | Another record with the same series and folio number, or with the same key, changed at the same time. Repeat the request. | Otro ticket con la misma serie y folio o la misma Idempotency-Key cambió mientras se ingería este. Intenta de nuevo. |
| 422 | The key was already used with a different body. | La Idempotency-Key ya se usó con otro cuerpo: una key identifica un solo ticket. Usa una key nueva para otro ticket. |
| 422 | The total doesn't match to the cent. The total you sent is in data.expectedTotal, and the calculated one is in data.computedTotal. | 'total' no cuadra al centavo con el total calculado de los conceptos e impuestos: 'data.expectedTotal' es el enviado y 'data.computedTotal' el calculado. |
| 422 | The payment date is more than 5 minutes in the future. | 'paymentDate' no puede ser posterior a ahora por más de 5 minutos: revisa el reloj del punto de venta. |
| 422 | The issuing person doesn't belong to your tenant. | 'issuer.id' no es una persona de tu organización. |
| 422 | The issuing person is missing a tax detail. | La persona emisora no tiene <dato>: complétalo en sus datos fiscales para ingerir tickets. |
| 422 | The issuing person doesn't have a current CSD. | La persona emisora no tiene un CSD vigente (certificado y llave privada): cárgalo para ingerir tickets. |
| 422 | The issuing person has no stamps. | La persona emisora no tiene timbres: cada autofactura consume uno de su saldo. Transfiérele timbres para ingerir tickets. |
Catalog keys and decimals use the same messages as invoice issuance: see SAT catalog keys and Decimal places in amounts, quantities, and rates.
Request
curl --location 'https://test.fiscalapi.com/api/v4/tickets' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--header 'Idempotency-Key: pos-01-F-1001' \
--data-raw '{
"issuer": { "id": "<person_id>" },
"folio": "F-1001",
"series": "A",
"paymentDate": "2026-10-10T12:30:00",
"paymentFormCode": "01",
"total": 116.00,
"items": [
{
"itemCode": "50202306",
"itemSku": "SKU-1",
"quantity": 1,
"unitOfMeasurementCode": "H87",
"description": "Refresco",
"unitPrice": 100.00,
"taxObjectCode": "02",
"itemTaxes": [
{ "taxCode": "002", "taxTypeCode": "Tasa", "taxRate": "0.160000", "taxFlagCode": "T" }
]
}
]
}'
Response
{
"data": {
"id": "9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f",
"ticketCode": "K7M2Q9X4TB3H",
"qrUrl": "https://test.factura.fiscalapi.com/t/K7M2Q9X4TB3H",
"status": "pending",
"selfInvoiceEligible": true,
"eligibleUntil": "2026-10-31T23:59:59.000",
"subtotal": 100,
"totalTransferredTaxes": 16,
"totalWithheldTaxes": 0,
"total": 116
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 201
}
List tickets
This endpoint returns your tenant's tickets, paginated and sorted by payment date. Each filter applies only if you send it. Use it to gather a period's pending tickets and build your global invoice for tickets.
For a grid, GET /api/v4/tickets/load returns your tenant's tickets with the DevExtreme load options (filtering, sorting, paging, and grouping) and returns its LoadResult, without the response envelope. It validates its parameters like the other load queries; see ValidationFailure.
Query parameters
- Name
status- Type
- string?
- Description
One or more statuses in camelCase. For several, repeat the parameter; for example,
status=pending&status=invoiced.
- Name
personId- Type
- string?
- Description
The ID of the issuing person. A person from another tenant returns an empty list.
- Name
globalInvoiceId- Type
- string?
- Description
The ID of the global invoice that covers the tickets. An invoice from another tenant returns an empty list.
- Name
from- Type
- string?
- Description
The first payment day, inclusive, as a Mexico local date in YYYY-MM-DD format. It goes together with
to.
- Name
to- Type
- string?
- Description
The last payment day, inclusive, as a Mexico local date in YYYY-MM-DD format. It goes together with
from.
- Name
details- Type
- boolean?
- Description
With
true, each ticket includes its line items with their taxes.Default:false
- Name
pageNumber- Type
- integer | number?
- Description
The page number, starting at 1.
Default:1
- Name
pageSize- Type
- integer | number?
- Description
Tickets per page, from 1 to 50.
Default:10
Errors
Each failure arrives as a validation failure on the parameter. In the messages, <valor> is the value received.
| Status | When it happens | Message |
|---|---|---|
| 400 | A status doesn't exist. | 'status' no admite '<valor>': usa pending, invoicing, invoiced, globalizing, globalized, cancelled (repite 'status' para varios). |
| 400 | Only one of the two period bounds is sent. | 'from' y 'to' van juntos: envía ambos (fechas YYYY-MM-DD de México) o ninguno. |
| 400 | The from date isn't in YYYY-MM-DD format. | 'from' debe ser una fecha YYYY-MM-DD. |
| 400 | The to date isn't in YYYY-MM-DD format. | 'to' debe ser una fecha YYYY-MM-DD. |
| 400 | The period's start is after its end. | 'from' no puede ser posterior a 'to'. |
| 400 | The page size isn't between 1 and 50. | 'pageSize' debe estar entre 1 y 50. |
Request
curl --location 'https://test.fiscalapi.com/api/v4/tickets?status=pending&personId=<person_id>&from=2026-10-01&to=2026-10-31&pageNumber=1&pageSize=50' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"items": [
{
"id": "9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f",
"personId": "4b1c2d3e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
"folio": "F-1001",
"series": "A",
"paymentDate": "2026-10-10T12:30:00.000",
"paymentFormCode": "01",
"currencyCode": "MXN",
"exchangeRate": null,
"expeditionZipCode": "42501",
"subtotal": 100,
"totalTransferredTaxes": 16,
"totalWithheldTaxes": 0,
"total": 116,
"status": "pending",
"selfInvoiceEligible": true,
"eligibleUntil": "2026-10-31T23:59:59.000",
"invoiceId": null,
"globalInvoiceId": null,
"createdAt": "2026-10-10T12:31:00.000"
}
],
"pageNumber": 1,
"totalPages": 1,
"totalCount": 1,
"hasPreviousPage": false,
"hasNextPage": false
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Get a ticket by ID
This endpoint returns a ticket with its status, its invoices, and its auto-invoice window. With details=true, it also includes its line items with their taxes and its history.
Query parameters
- Name
details- Type
- boolean?
- Description
With
true, it includes the ticket's line items and history.Default:false
Errors
In the message, which arrives in details, <id> is the ticket ID.
| Status | When it happens | Message |
|---|---|---|
| 404 | The ticket doesn't exist or belongs to another tenant. | No such resource for the given Id: <id> |
Request
curl --location 'https://test.fiscalapi.com/api/v4/tickets/<ticket_id>?details=true' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"id": "9f8e7d6c-1b2a-4c3d-8e9f-0a1b2c3d4e5f",
"personId": "4b1c2d3e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
"folio": "F-1001",
"series": "A",
"paymentDate": "2026-10-10T12:30:00.000",
"paymentFormCode": "01",
"currencyCode": "MXN",
"exchangeRate": null,
"expeditionZipCode": "42501",
"subtotal": 100,
"totalTransferredTaxes": 16,
"totalWithheldTaxes": 0,
"total": 116,
"status": "pending",
"selfInvoiceEligible": true,
"eligibleUntil": "2026-10-31T23:59:59.000",
"invoiceId": null,
"globalInvoiceId": null,
"createdAt": "2026-10-10T12:31:00.000",
"items": [
{
"itemCode": "50202306",
"itemSku": "SKU-1",
"quantity": 1,
"unitOfMeasurementCode": "H87",
"description": "Refresco",
"unitPrice": 100,
"discount": 0,
"taxObjectCode": "02",
"itemTaxes": [
{ "taxCode": "002", "taxTypeCode": "Tasa", "taxRate": 0.16, "taxFlagCode": "T" }
]
}
],
"events": [
{ "type": "ingested", "occurredAt": "2026-10-10T12:31:00.000", "actorType": "tenant", "invoiceId": null }
]
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Get a ticket code
This endpoint returns the ticket code and its QR code URL, the same ones that Create a ticket returned. Use it to reprint a sales receipt.
Errors
In the message, which arrives in details, <id> is the ticket ID.
| Status | When it happens | Message |
|---|---|---|
| 404 | The ticket doesn't exist or belongs to another tenant. | No such resource for the given Id: <id> |
Request
curl --location 'https://test.fiscalapi.com/api/v4/tickets/<ticket_id>/code' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"ticketCode": "K7M2Q9X4TB3H",
"qrUrl": "https://test.factura.fiscalapi.com/t/K7M2Q9X4TB3H"
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Cancel a ticket
This endpoint cancels a ticket in pending status and releases its series and folio number so you can record it again. The ticket credit used when it was recorded isn't refunded. It requires Auto CFDI invoicing in your subscription.
You don't cancel an invoiced ticket here: cancel its invoice. When the cancellation of its individual invoice is confirmed with cancellation reason (one of the SAT keys 01 to 04) 02 (issued with errors, no replacement CFDI) or 03 (the transaction did not take place), the ticket returns to pending.
With reason 01 (issued with errors, replaced by a related CFDI), it stays linked to the replacement CFDI (CFDI replacement: you issue a corrected CFDI related to the original with relationship type 04, then cancel the original with reason 01) if the replacement relates to the original with type 04. For the global invoice, see Replace a global invoice for tickets.
Errors
In the messages, <id> is the ticket ID and <estado> is its current status.
| Status | When it happens | Message |
|---|---|---|
| 403 | Your subscription doesn't include Auto CFDI invoicing. See Permissions and subscription. | Para cancelar tickets tu suscripción debe incluir: Auto CFDI. |
| 404 | The ticket doesn't exist or belongs to another tenant. | No such resource for the given Id: <id> |
| 409 | The ticket isn't in pending. | Solo se cancela un ticket en estado pending y este está en <estado>: ya se facturó, se está facturando o ya estaba cancelado. El crédito de ticket de la ingesta no se devuelve. |
| 409 | The ticket's status changed while it was being canceled. Repeat the request. | El ticket cambió de estado mientras se cancelaba (alguien lo estaba facturando). Intenta de nuevo. |
Request
curl --location --request DELETE 'https://test.fiscalapi.com/api/v4/tickets/<ticket_id>' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": true,
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Create a global invoice for tickets
This endpoint stamps the global invoice with the standard request of Create an invoice. With ticketId, Fiscalapi reserves the tickets before it deducts the stamp: all of them or none. When the CFDI is saved, the tickets move to globalized, with the invoice ID in globalInvoiceId. If the PAC (Proveedor Autorizado de Certificación, a SAT-authorized provider that stamps CFDIs) rejects the CFDI, the tickets return to their previous status and the stamp is refunded.
The example covers ticket F-1002, a sale of 166.00 with a soft drink of 100.00 at 16% IVA (Mexico's value-added tax, VAT) and still water of 50.00 at 0%. Its two tax groups go in two line items with the same ticketId, and each line item adds up what the ticket stored in its group. Global invoice for tickets explains how to build the line items and the CFDI.
Rules for ticketId
The items[].ticketId field is optional. Without it, the invoice is issued as usual.
| Rule | What happens if it isn't met |
|---|---|
It goes only in an income global invoice, with typeCode I and globalInformation. | 400 on the line item's ticketId. |
| The global invoice carries the issuer by reference. | 400 on Issuer.Id. |
| The ticket belongs to your tenant and to the same issuing person, and is in the same currency as the global invoice. | 400 on the line item's ticketId. |
| The ticket's line items reflect the saved ticket. | 400 on the ticketId of the ticket's first line item. |
The ticket is in pending, or in globalized in the global invoice you're replacing. | 409 with data.unavailableTickets, without charging or reserving tickets. |
Retries
A 400 or a 409 doesn't charge the stamp or leave tickets reserved. On a 409, data.unavailableTickets contains the id and status of each ticket that was no longer available: query the pending tickets again and build the global invoice again. Don't retry the same request unchanged.
If the PAC doesn't respond, or responds with a server error and no rejection code, the result is uncertain because the CFDI may have been stamped. Fiscalapi returns the unexpected error and keeps the stamp, the folio number, and the reserved tickets so they can be reviewed. Don't issue another global invoice with those tickets: check your invoices and the tickets, and if they're still in globalizing, contact support with the traceIdentifier from the response.
Errors
Failures arrive as a validation failure, with the field in PascalCase; for example, Items[0].TicketId. In the messages, i is the line item's position, starting at 0.
| Status | When it happens | Message |
|---|---|---|
| 400 | A line item has a ticketId and the invoice isn't an income global invoice. | 'items[i].ticketId' solo aplica en una factura global de ingreso: 'typeCode' I con 'globalInformation'. Quita el ticketId o emite la factura global. |
| 400 | The ticketId is empty or longer than 36 characters. | 'items[i].ticketId' debe tener de 1 a 36 caracteres: el id del ticket. |
| 400 | The global invoice with tickets carries the issuer by value. It arrives on Issuer.Id. | La factura global con tickets requiere el emisor por referencia ('issuer.id'): la persona emisora de los tickets. |
| 400 | The ticket doesn't exist or belongs to another tenant. | 'items[i].ticketId' no es un ticket de tu organización. |
| 400 | The ticket belongs to another issuing person. | 'items[i].ticketId' es un ticket de otra persona emisora: la factura global lleva solo tickets de su emisor, indicado por referencia ('issuer.id'). |
| 400 | The ticket is in another currency. | 'items[i].ticketId' es un ticket en otra moneda: la factura global lleva solo tickets de su moneda ('currencyCode'). |
| 400 | The ticket's line items don't reflect the saved ticket. | 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. |
| 409 | A ticket is no longer available. The tickets and their status are in 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. |
In the message about line items that don't reflect the ticket, <conceptos> is the list of line items with that ticket; for example, items[0], items[1]. The invoice's other errors are those of Create a global invoice.
Request
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
}