Skip to content
Documentation sections

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

  1. When you charge, your system sends the ticket to POST /selfservice/ticket. It starts in status 1, not invoiced.
  2. Your customer opens your portal, types the ticket's reference and amount, and invoices it. The ticket moves to status 2.
  3. Whatever nobody invoiced goes into your global invoice and moves to status 3.
  4. If there's a return or a cancelled sale, move the ticket to status 4 with POST /selfservice/ticketstatus so it can no longer be invoiced.

Ticket statuses

StatusWhat it meansCan it be invoiced?Can the API set it?
1Not invoiced.Yes.Yes.
2Invoiced by your customer.No.No: stamping sets it.
3In a global invoice.No.No: the global invoice sets it.
4Cancelled or returned.No.Yes.
5To be reinvoiced: it was invoiced with the wrong data.Yes, within the month of its previous invoice.Yes.
6Voided.No.Yes.

Register a ticket

POST/selfservice/ticket

The 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
curl -sS -X POST "$FD_API/selfservice/ticket" \
  -H "X-Api-Key: $FD_API_KEY" \
  --data-urlencode "json@ticket.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" }
        ]
      }
    }
  ]
}
Referenciastringrequired
The ticket folio your customer will type in the portal. At least 5 characters.
Fechastringrequired
AAAA-MM-DD. Tickets older than 13 months aren't accepted.
FechaLimitestringrequired
The deadline to invoice it, AAAA-MM-DD hh:mm:ss. Include a time: 2026-11-30 with no time expires at the start of that day, and without FechaLimite the ticket is already expired when it's created.
Moneda, TipoCambiostringrequired
MXN with 1, or the currency and that day's exchange rate.
Subtotal, Totalstringrequired
Other than zero.
Ivastringrequired
Always send it, even if it's "0.00".
Descuentostringoptional
Ticket discount.
Sucursalstringoptional
Branch number.
Plataformastringoptional
Free text, for example TPV.
Correostringoptional
Your customer's email.
FormaPagostringoptional
c_FormaPago key.
Seriestringoptional
When the ticket is invoiced, it's replaced by the invoice series.
Conceptosarrayoptional
The same fields as in Create a CFDI, with Impuestos.Traslados and Impuestos.Retenciones, plus extra1 to extra10 (lowercase).
codeWhen
400The json parameter didn't arrive.
5501Referencia is empty or shorter than 5 characters. It also shows up if the JSON is malformed.
5503Fecha is missing or older than 13 months.
5504Moneda is missing.
5505TipoCambio is missing.
5506Subtotal is zero.
5507Total is zero.
520Duplicate 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

GET/selfservice/ticket

With the referencia, fecha (AAAA-MM-DD) and monto parameters in the URL. If it was invoiced, it carries the UUID, series, folio and recipient.

200
{
  "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

GET/selfservice/ticketlist
GET/selfservice/ticketlistdetailed

Parameters (query)

fechaini, fechafinstringrequired
Ticket date range, AAAA-MM-DD, inclusive.
estatusintegerconditional
A status from 1 to 6. Required in ticketlist, optional in ticketlistdetailed.
contabilizadostringoptional
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

POST/selfservice/ticketstatus

With 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[].

200
{
  "message": "Ticket updated. New Status: 4",
  "code": 200,
  "tickets": [
    { "fecha": "2026-11-03", "monto_total": 116.0, "estatus_anterior": 1, "estatus": 4, "resultado": "updated" }
  ]
}
  • resultado is updated, unchanged (it already had that status) or rejected, with the reason in motivo.
  • 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 code 409 with tickets[]: the ones that say updated did change.
  • code 503 “The service is busy” means another operation in your account had the turn: retry in a few seconds.

Mark as accounted for

POST/selfservice/ticketcontabilizado

With 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

POST/selfservice/ticketdelete

With 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.