Documentación para desarrolladores

API de Faqturia

Emite los 5 tipos de CFDI 4.0 y cancélalos ante el SAT, administra clientes y productos, consulta catálogos SAT y reportes, y recibe eventos firmados en tu sistema. La misma superficie está disponible para asistentes de IA vía MCP.

REST · JSONhttps://app.faqturia.com/api/v1Spec 2026.09.22-v141 operaciones

Inicio rápido

  1. En Faqturia ve a Integraciones → API Desarrolladores y crea una key con los scopes que necesites. Se muestra una sola vez: guárdala en tu gestor de secretos. Tiene la forma ktrl_live_….
  2. Comprueba a qué organización pertenece y si ya puede timbrar (pac_readiness):
    curl https://app.faqturia.com/api/v1/organization -H "X-API-Key: ktrl_live_xxxxxxxxxxxxxxxxxxxxxxxx"
  3. Emite tu primera factura con POST /invoices. Responde 201 solo cuando el CFDI ya está timbrado y guardado, con su UUID:
    curl -X POST "https://app.faqturia.com/api/v1/invoices" \
      -H "X-API-Key: ktrl_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: pedido-10482" \
      -d '{ "client_id": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f", "uso_cfdi": "G03", "forma_pago": "03", "metodo_pago": "PUE", "conceptos": [ { "descripcion": "Servicio de consultoría", "cantidad": 1, "precio_unitario": 10000, "clave_prod_serv": "80141600", "clave_unidad": "E48", "impuestos": true } ] }'
Timbrado en producción. Una organización en modo pruebas timbra con su CSD de pruebas y series TEST; esos CFDI no se entregan por API ni MCP. Para probar tu integración sin validez fiscal activa el modo pruebas desde la cabecera de la app. Si la organización no tiene PAC configurado, las operaciones que timbran responden 503 PAC_NOT_CONFIGURED; si agotó los timbres de su plan, 402 PLAN_LIMIT_EXCEEDED.

Autenticación y scopes

Cada key pertenece a una organización y solo ve sus datos. Envíala en X-API-Key o como Authorization: Bearer. Las keys se guardan con hash: si pierdes una, revócala y crea otra. Pueden tener fecha de expiración, límite por minuto y orígenes permitidos.

ScopeQué permite
readTodas las lecturas (incluye catálogos SAT y la organización)
clients:readLeer clients — GET /clients, GET /clients/{id}
clients:writeEscribir clients — POST /clients/create, PUT /clients/{id}, DELETE /clients/{id}
credit-notes:readLeer credit-notes — GET /credit-notes
credit-notes:writeEscribir credit-notes — POST /credit-notes
invoices:readLeer invoices — GET /invoices, GET /invoices/{id}, GET /invoices/{id}/pdf, GET /invoices/{id}/xml
invoices:writeEscribir invoices — POST /invoices, DELETE /invoices/{id}/cancel
payment-complements:readLeer payment-complements — GET /payment-complements
payment-complements:writeEscribir payment-complements — POST /payment-complements
payroll:readLeer payroll — GET /payroll
payroll:writeEscribir payroll — POST /payroll
products:readLeer products — GET /products, GET /products/{id}
products:writeEscribir products — POST /products, PUT /products/{id}, DELETE /products/{id}
reports:readLeer reports — GET /reports/sales, GET /reports/summary, GET /reports/taxes
transfers:readLeer transfers — GET /transfers
transfers:writeEscribir transfers — POST /transfers
webhooks:readLeer webhooks — GET /webhooks, GET /webhooks/{id}, GET /webhooks/{id}/logs
webhooks:writeEscribir webhooks — POST /webhooks, PATCH /webhooks/{id}, DELETE /webhooks/{id}, POST /webhooks/{id}/test
read cubre todas las lecturas y write todas las escrituras; write no implica read. Una operación sin el scope responde 403 FORBIDDEN_SCOPE con required_scopes.

Convenciones

Versionado

El prefijo canónico es /api/v1 y toda respuesta lleva X-Api-Version: 1. Los cambios incompatibles irán bajo un prefijo nuevo, nunca sobre v1. /api/external sigue funcionando como alias.

Idempotencia

Las operaciones que crean o cancelan CFDI aceptan Idempotency-Key (hasta 128 caracteres, 24 h). Misma clave y mismo cuerpo → la respuesta original con Idempotent-Replayed: true; cuerpo distinto → 422; en curso → 409.

Límite de peticiones

Por key y por minuto. Cada respuesta trae X-RateLimit-Limit, -Remaining y -Reset; al pasarte, 429 con Retry-After.

Paginación

Los listados aceptan page (desde 1) y pageSize (1–100, default 25). Una página fuera de rango devuelve una lista vacía, no un error.

Rastreo

Toda respuesta lleva X-Request-Id; si mandas el tuyo ([A-Za-z0-9._-], hasta 64) se respeta. Inclúyelo al reportar un problema.

Archivos

GET /invoices/:id/xml y /pdf devuelven URLs firmadas válidas por 1 hora; pídelas de nuevo cuando las necesites.

Errores

Todos los errores tienen la misma forma y un code estable para ramificar tu lógica:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "conceptos[0].cantidad: debe ser mayor que 0",
    "field": "conceptos[0].cantidad",
    "issues": [{ "field": "conceptos[0].cantidad", "message": "debe ser mayor que 0" }],
    "request_id": "6f1d2c9a-3b4e-4f5a-8c7d-2e1f0a9b8c7d"
  }
}
CódigoNota
UNAUTHORIZED
FORBIDDEN_SCOPE
FORBIDDEN_ORIGIN
RATE_LIMITED
RATE_LIMITER_UNAVAILABLE
VALIDATION_ERROR
NOT_FOUND
INVALID_STATUS
DUPLICATE_RFC
ALREADY_INACTIVE
ALREADY_ARCHIVED
ALREADY_CANCELLED
SERIES_NOT_CONFIGURED
STAMP_REJECTED
STAMP_PERSIST_ERROR
CANCELLATION_REJECTED
CANCELLATION_PERSIST_ERROR
PAC_NOT_CONFIGURED503: sin credenciales de PAC utilizables en este entorno; no se memoriza bajo Idempotency-Key, reintenta tras configurarlas
PLAN_LIMIT_EXCEEDED402: el plan de la organización agotó sus facturas timbradas; no se memoriza bajo Idempotency-Key, reintenta tras mejorar el plan
NOT_SUPPORTED
IDEMPOTENCY_KEY_REUSED
IDEMPOTENCY_IN_PROGRESS
IDEMPOTENCY_UNAVAILABLE
INTERNAL_ERROR

Ejemplos de código

Crear y timbrar una factura de ingreso. impuestos: true aplica IVA 16 %; también acepta una tasa o { traslados, retenciones }. La factura toma el siguiente folio de la serie activa.

Node.js (fetch)

const res = await fetch('https://app.faqturia.com/api/v1/invoices', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.FAQTURIA_API_KEY,
    'Content-Type': 'application/json',
    // Reintentar con la misma clave nunca timbra dos veces
    'Idempotency-Key': 'pedido-10482',
  },
  body: JSON.stringify({
    "client_id": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
    "uso_cfdi": "G03",
    "forma_pago": "03",
    "metodo_pago": "PUE",
    "conceptos": [
      {
        "descripcion": "Servicio de consultoría",
        "cantidad": 1,
        "precio_unitario": 10000,
        "clave_prod_serv": "80141600",
        "clave_unidad": "E48",
        "impuestos": true
      }
    ]
  }),
});

const body = await res.json();
if (!res.ok) {
  // { error: { code, message, request_id, field?, issues? } }
  throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);
}
console.log(body.data.uuid, body.data.serie, body.data.folio);

Python (requests)

import os, requests

res = requests.post(
    "https://app.faqturia.com/api/v1/invoices",
    headers={
        "X-API-Key": os.environ["FAQTURIA_API_KEY"],
        "Idempotency-Key": "pedido-10482",
    },
    json={
        "client_id": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
        "uso_cfdi": "G03",
        "forma_pago": "03",
        "metodo_pago": "PUE",
        "conceptos": [{
            "descripcion": "Servicio de consultoría",
            "cantidad": 1,
            "precio_unitario": 10000,
            "clave_prod_serv": "80141600",
            "clave_unidad": "E48",
            "impuestos": True,
        }],
    },
    timeout=60,
)
body = res.json()
if not res.ok:
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"]["uuid"])

PHP (cURL)

<?php
$ch = curl_init('https://app.faqturia.com/api/v1/invoices');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: ' . getenv('FAQTURIA_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: pedido-10482',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'client_id'   => '3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f',
        'uso_cfdi'    => 'G03',
        'forma_pago'  => '03',
        'metodo_pago' => 'PUE',
        'conceptos'   => [[
            'descripcion'     => 'Servicio de consultoría',
            'cantidad'        => 1,
            'precio_unitario' => 10000,
            'clave_prod_serv' => '80141600',
            'clave_unidad'    => 'E48',
            'impuestos'       => true,
        ]],
    ]),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new Exception($body['error']['code'] . ': ' . $body['error']['message']);
}
echo $body['data']['uuid'];

Todos los campos del cuerpo: referencia de POST /invoices.

Webhooks

Crea suscripciones desde Integraciones → Webhooks o con POST /webhooks. La URL debe ser pública (https o http, sin localhost ni redes privadas). Cada entrega es un POST JSON:

{
  "id": "0b8f6a52-6d0e-4a55-9a53-2f7a4b1c9e21",
  "event": "invoice.stamped",
  "api_version": "2026-09-18",
  "created_at": "2026-09-24T17:30:00.000Z",
  "organization_id": "95503bbb-…",
  "data": {
    "id": "c2a1…",
    "tipo_comprobante": "I",
    "serie": "A",
    "folio": "1043",
    "uuid": "5E2D6AF9-…",
    "status": "timbrada",
    "total": 11600
  }
}
invoice.createdinvoice.stampedinvoice.canceledinvoice.paidinvoice.partially_paidcredit_note.createdcredit_note.stampedcredit_note.canceledpayment_complement.createdpayment_complement.stampedpayment_complement.canceledpayroll.createdpayroll.stampedpayroll.canceledtransfer.createdtransfer.stampedtransfer.canceledwebhook.test
CabeceraContenido
X-Webhook-IdId del evento; es el mismo en cada reintento: deduplica por él
X-Webhook-EventTipo de evento
X-Webhook-TimestampFecha del evento (ISO 8601)
X-Webhook-AttemptNúmero de intento (1–8)
X-Webhook-Signaturet=<unix>,v1=<HMAC-SHA256 del texto "<t>.<cuerpo>" con tu secret>

Verifica la firma con el cuerpo crudo y rechaza marcas de tiempo de más de 5 minutos:

Node.js

import crypto from 'node:crypto';

// rawBody: el cuerpo EXACTO recibido (no lo re-serialices)
export function verifyFaqturiaWebhook(rawBody, signatureHeader, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // anti-replay

  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(parts.v1, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python

import hmac, hashlib, time

def verify_faqturia_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in signature_header.split(","))
    t, v1 = int(parts.get("t", "0")), parts.get("v1", "")
    if not v1 or abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

PHP

<?php
function verify_faqturia_webhook(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(',', $header) as $p) {
        [$k, $v] = array_map('trim', explode('=', $p, 2));
        $parts[$k] = $v;
    }
    $t = (int) ($parts['t'] ?? 0);
    if (empty($parts['v1']) || abs(time() - $t) > $tolerance) return false;
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1']);
}
Responde 2xx en menos de 30 s. Si no, se reintenta 7 veces con espera creciente (1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h) y al octavo fallo la entrega queda como fallida. El historial de entregas y el envío de un evento webhook.test firmado están en la app y en la API.

Servidor MCP

https://app.faqturia.com/api/mcp expone 45 herramientas (33 de lectura y 12 de escritura) que ejecutan exactamente las mismas operaciones que esta API, con la misma API key y los mismos scopes. Las de escritura que timbran exigen idempotency_key, así que un asistente que reintenta no duplica CFDI. Las herramientas fiscales (bóveda, conciliación, alertas y Monitor Fiscal) piden el scope fiscal:read.

Claude.ai y ChatGPT (OAuth)

Sin API key: agrega Faqturia como conector con el Client ID de Integraciones → MCP (IA) e inicia sesión con tu cuenta de Kontrolia. Sin credencial, /api/mcp responde 401 con resource_metadata (RFC 9728) para que el cliente descubra el servidor de autorización. El acceso es de solo lectura y con los permisos del usuario: nunca timbra ni cancela.

Claude Desktop y otros clientes MCP (API key)

{
  "mcpServers": {
    "faqturia": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "https://app.faqturia.com/api/mcp",
        "--header", "Authorization: Bearer ktrl_live_xxxxxxxxxxxxxxxxxxxxxxxx"
      ]
    }
  }
}
Para un asistente, crea una key solo con los scopes que deba usar (por ejemplo read); sin *:write no puede timbrar ni cancelar. La app muestra la URL exacta de tu instalación en Integraciones → MCP (IA).

Referencia

Todas las operaciones

Generada de la especificación OpenAPI 2026.09.22-v1: lo que ves aquí es lo que la API valida. Los campos no listados se rechazan con VALIDATION_ERROR. Las respuestas completas están en la especificación.

Organización

GET/api/v1/organization

La organización de la API key ("quién soy") y si puede timbrar

Razón social, RFC, régimen, lugar de expedición, series con su folio siguiente y pac_readiness: si la organización puede timbrar en este entorno (credenciales de PAC cargadas y descifrables) y, si no, la causa y qué hacer. Consúltalo antes de integrar el timbrado: un ready: false explica de antemano el 503 PAC_NOT_CONFIGURED.

Scopes (cualquiera): read

200401403404429500

Facturas

GET/api/v1/invoices

Listar facturas de ingreso (CFDI I)

Scopes (cualquiera): readinvoices:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringBúsqueda difusa por folio, serie o fragmento de UUID
statusquery"draft" | "pendiente" | "timbrada" | "parcialmente_pagada" | "pagada" | "cancelada"Estado del CFDI
client_idquerystring (uuid)Solo documentos de este cliente
date_fromquerystring (date)Fecha de emisión desde (YYYY-MM-DD)
date_toquerystring (date)Fecha de emisión hasta (YYYY-MM-DD, inclusive)
seriequerystringSerie exacta
folioquerystringFolio exacto
uuidquerystringUUID fiscal (búsqueda parcial, sin comodines)
sortquery"fecha_emision" | "total" | "folio" | "created_at"Campo de ordendefault "fecha_emision"
orderquery"asc" | "desc"Direccióndefault "desc"
metodo_pagoquery"PUE" | "PPD"Método de pago
200400401403429500
POST/api/v1/invoices

Crear y timbrar una factura de ingreso

Reserva el folio de la serie activa de forma atómica, timbra con el PAC configurado y responde 201 solo tras releer la fila persistida con UUID. conceptos[].impuestos acepta true (IVA 16 %), una tasa numérica o { traslados: [{ impuesto, tasa }], retenciones: [...] }. auto_stamp=false no está soportado. Factura global: informacion_global { periodicidad, meses, anio } con receptor XAXX010101000 / PUBLICO EN GENERAL / 616 / S01 (si no, 422 FACTURA_GLOBAL_INVALIDA). Factura de flete: carta_porte con el mismo contenido que el cuerpo de POST /transfers (tipo_transporte, ubicaciones, mercancias, autotransporte, figura_transporte…); cada concepto debe ser una clave de servicio de transporte (p. ej. 78101802, CP109) o responde 422 CARTA_PORTE_INGRESO_INVALIDA sin timbrar.

Scopes (cualquiera): invoices:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
client_idstring (uuid)Requerido
conceptosarray<object>Requeridoal menos 1
conceptos[].cantidadnumberRequerido
conceptos[].descripcionstringRequerido
conceptos[].precio_unitarionumberOpcional
conceptos[].valor_unitarionumberOpcional
conceptos[].clave_satstringOpcional
conceptos[].clave_prod_servstringOpcional
conceptos[].unidadstringOpcional
conceptos[].clave_unidadstringOpcional
conceptos[].descuentonumberOpcional
conceptos[].impuestosanyOpcional
conceptos[].objeto_impuestostringOpcional
conceptos[].no_identificacionstringOpcional
conceptos[].skustringOpcional
conceptos[].unidad_nombrestringOpcional
seriestringOpcionalmáx. 10 caracteres
uso_cfdistringOpcionalpatrón ^[A-Z]\d{2}$
forma_pagostringOpcionalpatrón ^\d{2}$
metodo_pago"PUE" | "PPD"Opcional
monedastringOpcionalpatrón ^[A-Z]{3}$
tipo_cambionumberOpcionalmín. 0
exportacionstringOpcionalpatrón ^\d{2}$
lugar_expedicionstringOpcionalpatrón ^\d{5}$
fecha_emisionstringOpcional
fecha_vencimientostringOpcional
notasstringOpcionalmáx. 2000 caracteres · acepta null
informacion_globalobjectOpcional
informacion_global.periodicidad"01" | "02" | "03" | "04" | "05"Requerido
informacion_global.mesesstringRequeridopatrón ^(0[1-9]|1[0-8])$
informacion_global.aniointegerRequeridomín. 2000 · máx. 2100
carta_porteobjectOpcional
auto_stampanyOpcional
201400401402403404409422429500503
GET/api/v1/invoices/{id}

Detalle de una factura (cliente y conceptos incluidos)

Scopes (cualquiera): readinvoices:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500
DELETE/api/v1/invoices/{id}/cancel

Cancelar un CFDI timbrado ante el SAT

motivo 01 exige folio_sustitucion (UUID del CFDI que sustituye). Responde 200 solo cuando la fila quedó cancelada.

Scopes (cualquiera): invoices:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres
id *pathstring (uuid)Id de Faqturia del recurso

Cuerpo (JSON)

CampoTipoDescripción
motivo"01" | "02" | "03" | "04"Opcional
folio_sustitucionstring (uuid)Opcionalacepta null
200400401403404409422429500503
GET/api/v1/invoices/{id}/pdf

URL firmada (1 h) del PDF

Scopes (cualquiera): readinvoices:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
sourcequery"own" | "pac"own = PDF con la plantilla de la organización; pac = el original del PACdefault "own"
200401403404429500
GET/api/v1/invoices/{id}/xml

URL firmada (1 h) del XML/CFDI

Scopes (cualquiera): readinvoices:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500

Notas de crédito

GET/api/v1/credit-notes

Listar notas de crédito (CFDI E)

Scopes (cualquiera): readcredit-notes:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringBúsqueda difusa por folio, serie o fragmento de UUID
statusquery"draft" | "pendiente" | "timbrada" | "parcialmente_pagada" | "pagada" | "cancelada"Estado del CFDI
client_idquerystring (uuid)Solo documentos de este cliente
date_fromquerystring (date)Fecha de emisión desde (YYYY-MM-DD)
date_toquerystring (date)Fecha de emisión hasta (YYYY-MM-DD, inclusive)
seriequerystringSerie exacta
folioquerystringFolio exacto
uuidquerystringUUID fiscal (búsqueda parcial, sin comodines)
sortquery"fecha_emision" | "total" | "folio" | "created_at"Campo de ordendefault "fecha_emision"
orderquery"asc" | "desc"Direccióndefault "desc"
original_invoice_idquerystring (uuid)Solo notas que abonan a esta factura
200400401403429500
POST/api/v1/credit-notes

Crear y timbrar una nota de crédito

Relaciona (tipo 01) la factura de ingreso timbrada original_invoice_id, que debe pertenecer al mismo cliente.

Scopes (cualquiera): credit-notes:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
client_idstring (uuid)Requerido
original_invoice_idstring (uuid)Requerido
conceptosarray<object>Requeridoal menos 1
conceptos[].cantidadnumberRequerido
conceptos[].descripcionstringRequerido
conceptos[].precio_unitarionumberOpcional
conceptos[].valor_unitarionumberOpcional
conceptos[].clave_satstringOpcional
conceptos[].clave_prod_servstringOpcional
conceptos[].unidadstringOpcional
conceptos[].clave_unidadstringOpcional
conceptos[].descuentonumberOpcional
conceptos[].impuestosanyOpcional
conceptos[].objeto_impuestostringOpcional
conceptos[].no_identificacionstringOpcional
conceptos[].skustringOpcional
conceptos[].unidad_nombrestringOpcional
uso_cfdistringOpcionalpatrón ^[A-Z]\d{2}$
forma_pagostringOpcionalpatrón ^\d{2}$
metodo_pago"PUE" | "PPD"Opcional
monedastringOpcionalpatrón ^[A-Z]{3}$
tipo_cambionumberOpcionalmín. 0
lugar_expedicionstringOpcionalpatrón ^\d{5}$
fecha_emisionstringOpcional
notasstringOpcionalmáx. 2000 caracteres · acepta null
auto_stampanyOpcional
201400401402403404409422429500503

Complementos de pago

GET/api/v1/payment-complements

Listar complementos de pago (REP, CFDI P)

Scopes (cualquiera): readpayment-complements:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringBúsqueda difusa por folio, serie o fragmento de UUID
statusquery"draft" | "pendiente" | "timbrada" | "parcialmente_pagada" | "pagada" | "cancelada"Estado del CFDI
client_idquerystring (uuid)Solo documentos de este cliente
date_fromquerystring (date)Fecha de emisión desde (YYYY-MM-DD)
date_toquerystring (date)Fecha de emisión hasta (YYYY-MM-DD, inclusive)
seriequerystringSerie exacta
folioquerystringFolio exacto
uuidquerystringUUID fiscal (búsqueda parcial, sin comodines)
sortquery"fecha_emision" | "total" | "folio" | "created_at"Campo de ordendefault "fecha_emision"
orderquery"asc" | "desc"Direccióndefault "desc"
200400401403429500
POST/api/v1/payment-complements

Crear y timbrar un complemento de pago (REP)

Cada documento relacionado se resuelve contra tus facturas timbradas con saldo; importe_pagado no puede exceder el saldo. Tras timbrar se registra un pago por documento (monto_pagado/status de cada factura se actualizan).

Scopes (cualquiera): payment-complements:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
client_idstring (uuid)Requerido
fecha_pagostringRequerido
forma_pagostringOpcionalpatrón ^\d{2}$
monedastringOpcionalpatrón ^[A-Z]{3}$
tipo_cambionumberOpcionalmín. 0
numero_operacionstringOpcionalmáx. 100 caracteres
banco_ordenantestringOpcionalmáx. 300 caracteres
cuenta_ordenantestringOpcionalmáx. 50 caracteres
cuenta_beneficiariostringOpcionalmáx. 50 caracteres
lugar_expedicionstringOpcionalpatrón ^\d{5}$
documentos_relacionadosarray<object>Requeridoal menos 1
documentos_relacionados[].invoice_idstring (uuid)Opcional
documentos_relacionados[].uuidstring (uuid)Opcional
documentos_relacionados[].importe_pagadonumberRequeridomín. 0
documentos_relacionados[].numero_parcialidadintegerOpcionalmín. 1
documentos_relacionados[].importe_saldo_anteriornumberOpcionalmín. 0
documentos_relacionados[].equivalencianumberOpcionalmín. 0
auto_stampanyOpcional
201400401402403404409422429500503

Nómina

GET/api/v1/payroll

Listar recibos de nómina (CFDI N)

Scopes (cualquiera): readpayroll:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringBúsqueda difusa por folio, serie o fragmento de UUID
statusquery"draft" | "pendiente" | "timbrada" | "parcialmente_pagada" | "pagada" | "cancelada"Estado del CFDI
client_idquerystring (uuid)Solo documentos de este cliente
date_fromquerystring (date)Fecha de emisión desde (YYYY-MM-DD)
date_toquerystring (date)Fecha de emisión hasta (YYYY-MM-DD, inclusive)
seriequerystringSerie exacta
folioquerystringFolio exacto
uuidquerystringUUID fiscal (búsqueda parcial, sin comodines)
sortquery"fecha_emision" | "total" | "folio" | "created_at"Campo de ordendefault "fecha_emision"
orderquery"asc" | "desc"Direccióndefault "desc"
200400401403429500
POST/api/v1/payroll

Crear y timbrar un recibo de nómina

El empleado se lee de tu catálogo de empleados (employee_id); percepciones, deducciones y otros pagos con códigos SAT de 3 dígitos.

Scopes (cualquiera): payroll:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
employee_idstring (uuid)Requerido
fecha_pagostringRequerido
fecha_inicial_pagostringRequerido
fecha_final_pagostringRequerido
num_dias_pagadosnumberRequeridomín. 0
tipo_nomina"O" | "E"Opcional
percepcionesarray<object>Requeridoal menos 1
percepciones[].tipo_percepcionstringRequeridopatrón ^\d{3}$
percepciones[].clavestringRequeridomín. 1 caracteres · máx. 20 caracteres
percepciones[].conceptostringOpcionalmáx. 200 caracteres
percepciones[].importe_gravadonumberOpcionalmín. 0
percepciones[].importe_exentonumberOpcionalmín. 0
deduccionesarray<object>Opcional
deducciones[].tipo_deduccionstringRequeridopatrón ^\d{3}$
deducciones[].clavestringRequeridomín. 1 caracteres · máx. 20 caracteres
deducciones[].conceptostringOpcionalmáx. 200 caracteres
deducciones[].importenumberRequeridomín. 0
otros_pagosarray<object>Opcional
otros_pagos[].tipo_otro_pagostringRequeridopatrón ^\d{3}$
otros_pagos[].clavestringRequeridomín. 1 caracteres · máx. 20 caracteres
otros_pagos[].conceptostringOpcionalmáx. 200 caracteres
otros_pagos[].importenumberRequeridomín. 0
otros_pagos[].subsidio_causadonumberOpcionalmín. 0
emisorobjectOpcional
emisor.curpstringOpcional
emisor.registro_patronalstringOpcional
emisor.rfc_patron_origenstringOpcional
codigo_postalstringOpcionalpatrón ^\d{5}$
lugar_expedicionstringOpcionalpatrón ^\d{5}$
empleadoobjectOpcional
auto_stampanyOpcional
201400401402403404409422429500503

Traslados

GET/api/v1/transfers

Listar CFDI de traslado / Carta Porte (CFDI T)

Scopes (cualquiera): readtransfers:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringBúsqueda difusa por folio, serie o fragmento de UUID
statusquery"draft" | "pendiente" | "timbrada" | "parcialmente_pagada" | "pagada" | "cancelada"Estado del CFDI
client_idquerystring (uuid)Solo documentos de este cliente
date_fromquerystring (date)Fecha de emisión desde (YYYY-MM-DD)
date_toquerystring (date)Fecha de emisión hasta (YYYY-MM-DD, inclusive)
seriequerystringSerie exacta
folioquerystringFolio exacto
uuidquerystringUUID fiscal (búsqueda parcial, sin comodines)
sortquery"fecha_emision" | "total" | "folio" | "created_at"Campo de ordendefault "fecha_emision"
orderquery"asc" | "desc"Direccióndefault "desc"
200400401403429500
POST/api/v1/transfers

Crear y timbrar un CFDI de traslado con Carta Porte

Origen = domicilio fiscal de tu organización; Destino = el cliente client_id (o domicilio en cada ubicación). Medios: autotransporte (01: distancia, vehículo, seguro y un Operador con licencia), marítimo (02: transporte_maritimo, estaciones con navegacion_trafico, peso_bruto/peso_neto por mercancía, sin distancia), aéreo (03: transporte_aereo, estaciones, valor_mercancia, sin distancia) y ferroviario (04: transporte_ferroviario, ubicaciones como lista con al menos 5 destinos, CP129). facturas_relacionadas liga el traslado con las facturas I/E que ampara (TipoRelacion 05). Todo se valida antes del PAC: un 422 no timbra ni consume folio.

Scopes (cualquiera): transfers:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
client_idstring (uuid)Requerido
fecha_trasladostringRequerido
tipo_transporte"01" | "02" | "03" | "04"Opcional
permiso_sctstringOpcionalmáx. 20 caracteres
num_permiso_sctstringOpcionalmáx. 50 caracteres
ubicacionesobject | array<object>Requerido
distancia_recorridanumberOpcionalmín. 0
mercanciasarray<object>Requeridoal menos 1
mercancias[].clave_producto_serviciostringOpcionalpatrón ^\d{8}$
mercancias[].clave_satstringOpcionalpatrón ^\d{8}$
mercancias[].clave_unidadstringRequeridomín. 1 caracteres
mercancias[].unidadstringOpcional
mercancias[].descripcionstringRequeridomín. 1 caracteres
mercancias[].cantidadnumberRequeridomín. 0
mercancias[].peso_kgnumberOpcionalmín. 0
mercancias[].peso_brutonumberOpcionalmín. 0
mercancias[].peso_netonumberOpcionalmín. 0
mercancias[].num_piezasintegerOpcionalmín. 1
mercancias[].valor_mercancianumberOpcionalmín. 0
mercancias[].monedastringOpcionalpatrón ^[A-Z]{3}$
figura_transporteanyOpcional
autotransporteobjectOpcional
transporte_maritimoobjectOpcional
transporte_aereoobjectOpcional
transporte_ferroviarioobjectOpcional
facturas_relacionadasarray<string | object>Opcional
auto_stampanyOpcional
201400401402403404409422429500503

Clientes

GET/api/v1/clients

Listar clientes

Scopes (cualquiera): readclients:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringRFC, razón social o email (difuso)
rfcquerystringRFC exacto
statusquery"all" | "active" | "inactive"Filtro por activosdefault "all"
200401403429500
POST/api/v1/clients/create

Crear un cliente (receptor)

Scopes (cualquiera): clients:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
razon_socialstringRequeridomín. 1 caracteres · máx. 300 caracteres
rfcstringRequeridopatrón ^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$
emailstring (email)Opcionalmáx. 200 caracteres · acepta null
telefonostringOpcionalmáx. 30 caracteres · acepta null
regimen_fiscalstringOpcionalpatrón ^\d{3}$
uso_cfdistringOpcionalpatrón ^[A-Z]\d{2}$
codigo_postalstringOpcionalpatrón ^\d{5}$
es_extranjerobooleanOpcional
201400401403409429500
GET/api/v1/clients/{id}

Detalle de un cliente

Scopes (cualquiera): readclients:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500
PUT/api/v1/clients/{id}

Actualizar un cliente (parcial)

Scopes (cualquiera): clients:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso

Cuerpo (JSON)

CampoTipoDescripción
razon_socialstringOpcionalmín. 1 caracteres · máx. 300 caracteres
rfcstringOpcionalpatrón ^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$
emailstring (email)Opcionalmáx. 200 caracteres · acepta null
telefonostringOpcionalmáx. 30 caracteres · acepta null
regimen_fiscalstringOpcionalpatrón ^\d{3}$
uso_cfdistringOpcionalpatrón ^[A-Z]\d{2}$
codigo_postalstringOpcionalpatrón ^\d{5}$
is_activebooleanOpcional
200400401403404429500
DELETE/api/v1/clients/{id}

Dar de baja un cliente (is_active = false)

Scopes (cualquiera): clients:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404409429500

Productos

GET/api/v1/products

Listar productos/servicios

Scopes (cualquiera): readproducts:read

Parámetros

NombreEnTipoDescripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
searchquerystringNombre, clave SAT o descripción
statusquery"all" | "active" | "archived"Estadodefault "all"
skuquerystringSKU exacto de una variante
200401403429500
POST/api/v1/products

Crear un producto con su variante por defecto

Scopes (cualquiera): products:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

CampoTipoDescripción
nombrestringRequeridomín. 1 caracteres · máx. 300 caracteres
descripcionstringOpcionalmáx. 2000 caracteres · acepta null
clave_satstringRequeridopatrón ^\d{8}$
clave_unidad_satstringOpcionalmín. 1 caracteres · máx. 5 caracteres
precio_unitarionumberRequeridomín. 0
tasa_ivanumberOpcionalmín. 0 · máx. 1
ieps_tasanumberOpcionalmín. 0 · máx. 1
objeto_impuestostringOpcionalpatrón ^\d{2}$
identificador_externostringOpcionalmáx. 60 caracteres · acepta null
peso_kgnumberOpcionalmín. 0 · acepta null
is_activebooleanOpcional
201400401403429500
GET/api/v1/products/{id}

Detalle de un producto

Scopes (cualquiera): readproducts:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500
PUT/api/v1/products/{id}

Actualizar un producto (parcial); precio/SKU/peso van a la variante por defecto

Scopes (cualquiera): products:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso

Cuerpo (JSON)

CampoTipoDescripción
nombrestringOpcionalmín. 1 caracteres · máx. 300 caracteres
descripcionstringOpcionalmáx. 2000 caracteres · acepta null
clave_satstringOpcionalpatrón ^\d{8}$
clave_unidad_satstringOpcionalmín. 1 caracteres · máx. 5 caracteres
precio_unitarionumberOpcionalmín. 0
tasa_ivanumberOpcionalmín. 0 · máx. 1
iva_tasanumberOpcionalmín. 0 · máx. 1
ieps_tasanumberOpcionalmín. 0 · máx. 1
objeto_impuestostringOpcionalpatrón ^\d{2}$
identificador_externostringOpcionalmáx. 60 caracteres · acepta null
peso_kgnumberOpcionalmín. 0 · acepta null
is_activebooleanOpcional
200400401403404429500
DELETE/api/v1/products/{id}

Archivar un producto

Scopes (cualquiera): products:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404409429500

Reportes

GET/api/v1/reports/sales

Ventas agrupadas por cliente, producto, mes o día

Scopes (cualquiera): readreports:read

Parámetros

NombreEnTipoDescripción
start_datequerystring (date)Desde
end_datequerystring (date)Hasta
group_byquery"client" | "product" | "month" | "date"Agrupacióndefault "client"
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
200400401403429500
GET/api/v1/reports/summary

Resumen del negocio en un periodo

Scopes (cualquiera): readreports:read

Parámetros

NombreEnTipoDescripción
start_datequerystring (date)Desde
end_datequerystring (date)Hasta
200400401403429500
GET/api/v1/reports/taxes

IVA/IEPS trasladados por mes, trimestre o año

Scopes (cualquiera): readreports:read

Parámetros

NombreEnTipoDescripción
start_datequerystring (date)Desde
end_datequerystring (date)Hasta
group_byquery"month" | "quarter" | "year"Periododefault "month"
200400401403429500

Webhooks

GET/api/v1/webhooks

Listar suscripciones (sin secret) y eventos disponibles

Scopes (cualquiera): readwebhooks:read

200401403429500
POST/api/v1/webhooks

Crear una suscripción

Scopes (cualquiera): webhooks:write

Parámetros

NombreEnTipoDescripción
Idempotency-KeyheaderstringClave única por operación. Misma clave + mismo cuerpo → se devuelve la respuesta original con Idempotent-Replayed: true sin repetir la operación; cuerpo distinto → 422 IDEMPOTENCY_KEY_REUSED; en curso → 409. Caduca a las 24 h.máx. 128 caracteres

Cuerpo (JSON)

La URL debe ser http(s) pública (sin localhost ni redes privadas). El secret (≥16 caracteres) firma cada entrega.

CampoTipoDescripción
urlstring (uri)Requeridomáx. 2000 caracteres
eventsarray<"invoice.created" | "invoice.stamped" | "invoice.canceled" | "invoice.paid" | "invoice.partially_paid" | "credit_note.created" | "credit_note.stamped" | "credit_note.canceled" | "payment_complement.created" | "payment_complement.stamped" | "payment_complement.canceled" | "payroll.created" | "payroll.stamped" | "payroll.canceled" | "transfer.created" | "transfer.stamped" | "transfer.canceled">Requeridoal menos 1
secretstringOpcionalmín. 16 caracteres · máx. 200 caracteres · acepta null
descriptionstringOpcionalmáx. 300 caracteres · acepta null
is_activebooleanOpcional
201400401403429500
GET/api/v1/webhooks/{id}

Detalle de una suscripción

Scopes (cualquiera): readwebhooks:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500
PATCH/api/v1/webhooks/{id}

Actualizar una suscripción (parcial)

Scopes (cualquiera): webhooks:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso

Cuerpo (JSON)

CampoTipoDescripción
urlstring (uri)Opcionalmáx. 2000 caracteres
eventsarray<"invoice.created" | "invoice.stamped" | "invoice.canceled" | "invoice.paid" | "invoice.partially_paid" | "credit_note.created" | "credit_note.stamped" | "credit_note.canceled" | "payment_complement.created" | "payment_complement.stamped" | "payment_complement.canceled" | "payroll.created" | "payroll.stamped" | "payroll.canceled" | "transfer.created" | "transfer.stamped" | "transfer.canceled">Opcionalal menos 1
secretstringOpcionalmín. 16 caracteres · máx. 200 caracteres · acepta null
descriptionstringOpcionalmáx. 300 caracteres · acepta null
is_activebooleanOpcional
200400401403404429500
DELETE/api/v1/webhooks/{id}

Eliminar una suscripción y sus logs

Scopes (cualquiera): webhooks:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500
GET/api/v1/webhooks/{id}/logs

Entregas de una suscripción

Scopes (cualquiera): readwebhooks:read

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
pagequeryintegerPáginamín. 1 · default 1
limitqueryintegerPor páginamín. 1 · máx. 100 · default 50
statusquery"pending" | "success" | "failed"Estado
event_typequerystringEvento
200401403404429500
POST/api/v1/webhooks/{id}/test

Enviar un evento webhook.test firmado y devolver el resultado

Scopes (cualquiera): webhooks:write

Parámetros

NombreEnTipoDescripción
id *pathstring (uuid)Id de Faqturia del recurso
200401403404429500

Catálogos SAT

GET/api/v1/catalogs/forma-pago

Catálogo SAT c_FormaPago

Scopes (cualquiera): read

200401403429500
GET/api/v1/catalogs/metodo-pago

Catálogo SAT c_MetodoPago

Scopes (cualquiera): read

200401403429500
GET/api/v1/catalogs/regimen-fiscal

Catálogo SAT c_RegimenFiscal

Scopes (cualquiera): read

200401403429500
GET/api/v1/catalogs/uso-cfdi

Catálogo SAT c_UsoCFDI

Scopes (cualquiera): read

200401403429500
GET/api/v1/catalogs/unidades

Catálogo SAT c_ClaveUnidad

Scopes (cualquiera): read

Parámetros

NombreEnTipoDescripción
searchquerystringClave, nombre o descripción
200401403429500
GET/api/v1/catalogs/productos-servicios

Catálogo SAT c_ClaveProdServ (usar search y paginar)

Scopes (cualquiera): read

Parámetros

NombreEnTipoDescripción
searchquerystringClave o descripción
pagequeryintegerPágina (empieza en 1)mín. 1 · default 1
pageSizequeryintegerResultados por páginamín. 1 · máx. 100 · default 25
200401403429500