Self-invoicing (kiosk)
Your point of sale registers each ticket with the API and your customer invoices it on their own in your self-invoicing portal, finding it by its reference and amount. These paths use no folios or credits.
How it works
- When you charge, your system sends the ticket to
POST /selfservice/ticket. It starts in status 1, not invoiced. - Your customer opens your portal, types the ticket's reference and amount, and invoices it. The ticket moves to status 2.
- Whatever nobody invoiced goes into your global invoice and moves to status 3.
- If there's a return or a cancelled sale, move the ticket to status 4 with
POST /selfservice/ticketstatusso it can no longer be invoiced.
Ticket statuses
| Status | What it means | Can it be invoiced? | Can the API set it? |
|---|---|---|---|
| 1 | Not invoiced. | Yes. | Yes. |
| 2 | Invoiced by your customer. | No. | No: stamping sets it. |
| 3 | In a global invoice. | No. | No: the global invoice sets it. |
| 4 | Cancelled or returned. | No. | Yes. |
| 5 | To be reinvoiced: it was invoiced with the wrong data. | Yes, within the month of its previous invoice. | Yes. |
| 6 | Voided. | No. | Yes. |
Register a ticket
/selfservice/ticketThe ticket goes as JSON text in the json parameter of a form. It responds { "message": "Ticket created successfully", "code": 200 }; to see the saved ticket, query it with GET /selfservice/ticket.
curl -sS -X POST "$FD_API/selfservice/ticket" \
-H "X-Api-Key: $FD_API_KEY" \
--data-urlencode "json@ticket.json"{
"Referencia": "POS-000123",
"Fecha": "2026-11-03",
"FechaLimite": "2026-11-30 23:59:59",
"Sucursal": "1",
"Plataforma": "TPV",
"Moneda": "MXN",
"TipoCambio": "1",
"Subtotal": "100.00",
"Descuento": "0.00",
"Iva": "16.00",
"Total": "116.00",
"FormaPago": "01",
"Conceptos": [
{
"ClaveProdServ": "50192100",
"NoIdentificacion": "SKU-001",
"Cantidad": "1",
"ClaveUnidad": "H87",
"Unidad": "Pieza",
"Descripcion": "Producto de prueba",
"ValorUnitario": "100.00",
"Importe": "100.00",
"Descuento": "0.00",
"Impuestos": {
"Traslados": [
{ "Base": "100.00", "Impuesto": "002", "TipoFactor": "Tasa", "TasaOCuota": "0.160000", "Importe": "16.00" }
]
}
}
]
}Referencia- The ticket folio your customer will type in the portal. At least 5 characters.
FechaAAAA-MM-DD. Tickets older than 13 months aren't accepted.FechaLimite- The deadline to invoice it,
AAAA-MM-DD hh:mm:ss. Include a time:2026-11-30with no time expires at the start of that day, and withoutFechaLimitethe ticket is already expired when it's created. Moneda, TipoCambioMXNwith1, or the currency and that day's exchange rate.Subtotal, Total- Other than zero.
Iva- Always send it, even if it's
"0.00". Descuento- Ticket discount.
Sucursal- Branch number.
Plataforma- Free text, for example
TPV. Correo- Your customer's email.
FormaPago- c_FormaPago key.
Serie- When the ticket is invoiced, it's replaced by the invoice series.
Conceptos- The same fields as in Create a CFDI, with
Impuestos.TrasladosandImpuestos.Retenciones, plusextra1toextra10(lowercase).
| code | When |
|---|---|
| 400 | The json parameter didn't arrive. |
| 5501 | Referencia is empty or shorter than 5 characters. It also shows up if the JSON is malformed. |
| 5503 | Fecha is missing or older than 13 months. |
| 5504 | Moneda is missing. |
| 5505 | TipoCambio is missing. |
| 5506 | Subtotal is zero. |
| 5507 | Total is zero. |
| 520 | Duplicate ticket: that reference with that total already exists. |
The API doesn't check that the totals match the line items: send them balanced so your customer's invoice goes through.
Query a ticket
/selfservice/ticketWith the referencia, fecha (AAAA-MM-DD) and monto parameters in the URL. If it was invoiced, it carries the UUID, series, folio and recipient.
{
"code": "200",
"message": "Ticket encontrado",
"data": {
"referencia": "POS-000123",
"fecha": "2026-11-03",
"fecha_limite": "2026-11-30 23:59:59",
"monto_total": "116.000000",
"estatus": "2",
"uuid": "A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D",
"serie": "K",
"folio": "1001",
"receptor_rfc": "XIA190128J61",
"receptor_nombre": "XENON INDUSTRIAL ARTICLES",
"contabilizado": null
}
}If it doesn't exist, it responds HTTP 200 with { "code": "404", "message": "No se encuentra el ticket." }. Here code is text.
Ticket lists
/selfservice/ticketlist/selfservice/ticketlistdetailedParameters (query)
fechaini, fechafin- Ticket date range,
AAAA-MM-DD, inclusive. estatus- A status from 1 to 6. Required in
ticketlist, optional inticketlistdetailed. contabilizado- Filters by the value you set with
ticketcontabilizado.
ticketlistdetailed adds each ticket's id and its line items in detalle, with their taxes in impuesto. Lists have no pagination: use short ranges, above all with the detailed one. With no results they respond HTTP 200 with code "404".
Change the status
/selfservice/ticketstatusWith referencia and estatus in the body (1, 4, 5 or 6; only invoicing sets 2 and 3). It changes every ticket in your account with that reference, one by one, and tells you what happened to each one in tickets[].
{
"message": "Ticket updated. New Status: 4",
"code": 200,
"tickets": [
{ "fecha": "2026-11-03", "monto_total": 116.0, "estatus_anterior": 1, "estatus": 4, "resultado": "updated" }
]
}resultadoisupdated,unchanged(it already had that status) orrejected, with the reason inmotivo.- A ticket isn't changed if it's already invoiced, if your customer is invoicing it at that moment or if it's going into a global invoice.
- If some changed and others didn't, it responds
code409 withtickets[]: the ones that sayupdateddid change. code503 “The service is busy” means another operation in your account had the turn: retry in a few seconds.
Mark as accounted for
/selfservice/ticketcontabilizadoWith referencia and contabilizado in the body, it saves free text of up to 20 characters (for example 1, SI or a journal entry number) on every ticket with that reference. Without contabilizado, it clears the value. Use it to filter the lists.
Delete a ticket
/selfservice/ticketdeleteWith referencia and monto_total in the body. Only a ticket in status 1 that nobody is invoicing gets deleted. Afterwards you can register that same reference and amount again. The response carries tickets[] with resultado deleted or rejected.
Checked against the production API v5 on 2026-11-03.