Empezar
Convenciones de la API
URL base, formato de errores, paginación por cursor, request ids y tipos de dato.
URL base
https://facty.mx/api/authenticated/{orgSlug}/...
{orgSlug} es el identificador de tu organización en Facty (lo ves en la URL de
la app y en la respuesta de /context). Debe coincidir con la organización de tu
llave, si no: 403 ORG_MISMATCH.
Hay dos rutas sin organización, porque sirven datos de referencia del SAT que son públicos:
GET /api/authenticated/catalogs/sat?type=...
GET /api/authenticated/catalogs/clave-unidad?q=...
GET /api/authenticated/catalogs/clave-prod-serv?q=...
Petición
- Los cuerpos van en JSON:
Content-Type: application/json. - Los identificadores de Facty (
clientId,invoiceId,productId,paymentId,accountId,empleadoId) son cuids — cadenas comoclx9aa3k80000abcd1234efgh. No son numéricos ni UUIDs. - El
uuidde un CFDI (folio fiscal del SAT) sí es un UUID. - Los importes se mandan como números, no como cadenas:
10000.5, no"10,000.50". - Las tasas de impuesto van en decimal, no en porcentaje:
0.16es 16%. - Las fechas de periodo van como
"YYYY-MM-DD". Las marcas de tiempo que devolvemos son ISO-8601 en UTC.
Respuesta
Los 2xx devuelven JSON. Los códigos que usamos:
| Código | Cuándo |
|---|---|
200 | Lectura correcta, o escritura que resolvió a un registro existente (upsert). |
201 | Se creó algo nuevo: una factura timbrada, un cliente, un producto. |
Dos endpoints devuelven binario en vez de JSON: /invoices/{id}/cfdi (un ZIP con
el XML y el PDF) y /invoices/{id}/acuse (el PDF del acuse de cancelación).
Errores
Todo 4xx y 5xx sale con la misma envoltura, para que un cliente que no es un
navegador nunca tenga que comparar contra el texto en español (que nos reservamos
el derecho de reescribir):
{
"error": "Forbidden: falta el permiso `invoices.create`.",
"code": "MISSING_SCOPE",
"requestId": "0f9c2a1b…",
"required": "invoices.create"
}
codees estable y legible por máquina. Ramifica sobre esto.requestIdcoincide con el headerX-Request-Id. Cítalo al reportar un problema.- Los
422traen ademásfieldErrors, un mapa plano de ruta → mensaje, listo para pintar junto al campo culpable:
{
"error": "Datos inválidos.",
"code": "VALIDATION_FAILED",
"requestId": "8de1…",
"fieldErrors": {
"items.0.claveProdServ": "Clave SAT inválida (8 dígitos)",
"usoCfdi": "Uso CFDI inválido"
}
}
La lista completa de códigos y qué hacer con cada uno está en Errores.
X-Request-Id
Toda respuesta lleva el header X-Request-Id. Si tú mandas uno en la
petición, te lo devolvemos tal cual, para que puedas correlacionar tus logs con
los nuestros:
curl -s "$FACTY_HOST/api/authenticated/$FACTY_ORG/invoices" \
-H "X-API-Key: $FACTY_KEY" \
-H "X-Request-Id: erp-job-2026-08-28-0042" -D -
Guárdalo junto a cada operación fiscal que registres de tu lado. Es lo primero que te vamos a pedir en soporte.
Paginación
Las listas paginan por cursor, no por número de página: en un catálogo que se
está escribiendo al mismo tiempo, un ?page=2 se salta o repite registros.
GET /clients?limit=50
GET /clients?limit=50&cursor=clx9aa…
La respuesta trae nextCursor. Cuando es null, llegaste al final.
{
"items": [ /* … */ ],
"nextCursor": "clx9zz…"
}
Límites de limit:
| Recurso | Máximo | Default |
|---|---|---|
/clients, /products, /empleados | 500 | 50 |
/invoices, /payments, /retenciones | 100 | 25 |
Recorrer una lista completa:
cursor=""
while :; do
resp=$(curl -s "$FACTY_HOST/api/authenticated/$FACTY_ORG/invoices?limit=100${cursor:+&cursor=$cursor}" \
-H "X-API-Key: $FACTY_KEY")
echo "$resp" | jq -c '.invoices[]'
cursor=$(echo "$resp" | jq -r '.nextCursor // empty')
[ -z "$cursor" ] && break
done
Impuestos: cómo se expresan
| Campo | Significado | Ejemplo |
|---|---|---|
iva | Tasa de IVA trasladado. Solo 0, 0.08 o 0.16. | 0.16 |
ivaExento | Booleano. El acto sí es objeto del impuesto pero no lo causa (educación, servicios médicos, casa habitación). Excluyente con una tasa de iva distinta de cero. | true |
ieps | Tasa de IEPS. | 0.08 |
iepsCuota | IEPS de cuota: pesos por unidad de medida, no un porcentaje. Excluyente con ieps. | 1.6 |
retIsr, retIva, retIeps | Tasas de retención. retIsr tope 0.35. | 0.106667 |
descuento | Tasa de descuento sobre el concepto. 0.10 es 10%. | 0.10 |
ivaExento no es "IVA en cero": son cosas distintas ante el SAT. Cero es una
tasa (TipoFactor Tasa, TasaOCuota 0.000000); exento es otro TipoFactor, sin
tasa ni importe. Usar uno por el otro es un CFDI mal emitido aunque el total
cuadre.
Fechas y zona horaria
Los CFDI se timbran contra la hora del PAC. Si mandas una fecha con offset, se
normaliza a UTC antes de enviarla. Para campos de periodo (nómina, retenciones)
usa "YYYY-MM-DD" a secas y evítate la ambigüedad.