Guia de Integracion

Punto de Venta (POS)

Conecta tu sistema POS a FiscaFacil para facturar ventas, consultar facturas recibidas y sincronizar con el SAT — todo via API, sin que tus usuarios necesiten entrar al dashboard.

Arquitectura

Tu POS actua como intermediario: registra cuentas, sube certificados (CSD/FIEL) y timbra facturas en nombre de cada cliente. Cada cliente es una empresa en FiscaFacil, y tu POS usa API keys con scoping para aislar el acceso por empresa y por accion.

POS Backend → API Key (scoped) → FiscaFacil API → PAC certificado → SAT

Requisitos previos

  • Cuenta en FiscaFacil (registro via API o dashboard)
  • CSD (.cer + .key + contrasena) de cada cliente — para timbrar facturas
  • FIEL (.cer + .key + contrasena) de cada cliente — para descargar facturas del SAT (opcional)
  • Entorno: Sandbox (pruebas) o Produccion

Flujo completo paso a paso

1

Registrar cuenta de usuario

Crea una cuenta para tu POS. Esta cuenta administrara todas las empresas de tus clientes.

curl -X POST https://api.fiscafacil.mx/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mi POS System",
    "email": "pos@miempresa.com",
    "password": "SecureP@ssword123"
  }'

Respuesta: usuario creado con ID.

2

Crear API key con scoping

Crea un API key con permisos restringidos. Puedes limitar por empresas, acciones permitidas, rate limit e IP.

curl -X POST https://api.fiscafacil.mx/api-keys \
  -H "Content-Type: application/json" \
  -d '{
    "email": "pos@miempresa.com",
    "password": "SecureP@ssword123",
    "name": "POS Production Key",
    "description": "Key para operaciones de facturacion desde POS",
    "allowedActions": [
      "empresa:view", "empresa:csd",
      "cfdi:timbrar", "cfdi:cancelar", "cfdi:view", "cfdi:create",
      "cfdi:descargar", "cfdi:leer",
      "cliente:create", "cliente:view",
      "producto:create", "producto:view"
    ],
    "rateLimit": 120,
    "expiresInDays": 365
  }'
Importante: El API key solo se muestra UNA VEZ en la respuesta. Guardalo de forma segura. Tiene el formato ff_<64 hex chars>.

Opciones de scoping:

CampoTipoDescripcion
allowedEmpresaIdsstring[]IDs de empresas permitidas. [] = todas.
allowedActionsstring[]Acciones permitidas. [] = todas del rol.
rateLimitnumberRequests por minuto. Default: 60, max: 10,000.
ipWhiteliststring[]IPs permitidas. [] = cualquier IP.
expiresInDaysnumberDias hasta expiracion. Omitir = sin expiracion.
3

Crear empresa por cada cliente

Cada cliente de tu POS necesita una empresa en FiscaFacil con su RFC y regimen fiscal.

curl -X POST https://api.fiscafacil.mx/empresas \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "rfc": "XAXX010101000",
    "razonSocial": "Tienda Don Juan SA de CV",
    "regimenFiscal": "601",
    "codigoPostal": "44100",
    "sandbox": false
  }'

Guarda el id de la empresa — lo necesitas como header x-empresa-id en todas las llamadas.

4

Subir CSD (certificado de sello digital)

El CSD permite timbrar facturas. Sube el .cer, .key y contrasena en base64. FiscaFacil valida que el RFC del certificado coincida con la empresa.

# Convertir archivos a base64
CER_B64=$(base64 -i certificado.cer)
KEY_B64=$(base64 -i llave.key)

curl -X POST https://api.fiscafacil.mx/empresas/{empresaId}/certificados \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}" \
  -H "Content-Type: application/json" \
  -d "{
    \"cerBase64\": \"$CER_B64\",
    \"keyBase64\": \"$KEY_B64\",
    \"password\": \"12345678a\"
  }"
Los certificados se encriptan con AES-256-GCM antes de almacenarse. La contrasena nunca se guarda en texto plano.
5

Subir FIEL (e.firma) — opcional

La FIEL permite descargar facturas recibidas del SAT. Solo necesaria si quieres consultar lo que tus clientes han recibido.

curl -X POST https://api.fiscafacil.mx/empresas/{empresaId}/fiel \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}" \
  -H "Content-Type: application/json" \
  -d '{
    "cerBase64": "<base64 del .cer de FIEL>",
    "keyBase64": "<base64 del .key de FIEL>",
    "password": "contrasena_fiel"
  }'
6

Timbrar una venta

Envia el XML sin sellar — FiscaFacil sella y timbra con el PAC certificado.

curl -X POST https://api.fiscafacil.mx/cfdi/timbrar \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}" \
  -H "Content-Type: application/json" \
  -d '{
    "receptor": {
      "rfc": "XAXX010101000",
      "nombre": "Cliente Mostrador",
      "usoCfdi": "S01",
      "regimenFiscal": "616",
      "codigoPostal": "44100"
    },
    "conceptos": [{
      "claveProdServ": "01010101",
      "claveUnidad": "H87",
      "descripcion": "Venta mostrador",
      "cantidad": 1,
      "valorUnitario": 100.00
    }],
    "formaPago": "01",
    "metodoPago": "PUE",
    "moneda": "MXN"
  }'

Respuesta incluye: UUID, XML timbrado, sello SAT, cadena original, y URL del PDF/XML.

7

Consultar facturas

Lista facturas emitidas y recibidas con filtros y paginacion.

# Listar facturas del mes actual
curl "https://api.fiscafacil.mx/cfdi?page=1&limit=50&tipo=EMITIDA" \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}"

# Buscar por RFC del receptor
curl "https://api.fiscafacil.mx/cfdi?receptorRfc=XAXX010101000" \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}"
8

Descargar factura por UUID (instantaneo)

Descarga una factura recibida del portal del SAT en 5-10 segundos. Requiere FIEL configurada.

curl -X POST https://api.fiscafacil.mx/cfdi/descargar-por-uuid \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}" \
  -H "Content-Type: application/json" \
  -d '{ "uuid": "6A8B1234-5678-9ABC-DEF0-123456789ABC" }'

Tres posibles respuestas: cache (ya existia), sat_portal (descargada), o sat_metadata_only (solo metadata, sin XML completo).

9

Sync masiva de facturas recibidas

Sincroniza todas las facturas recibidas de un mes completo. Se ejecuta en background.

# Iniciar sync del mes
curl -X POST https://api.fiscafacil.mx/cfdi/sync-recibidos \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}" \
  -H "Content-Type: application/json" \
  -d '{ "year": 2026, "month": 4 }'

# Consultar estado del sync
curl "https://api.fiscafacil.mx/cfdi/sync-recibidos" \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}"
10

Configurar webhooks

Recibe notificaciones cuando una factura se timbra, cancela, o cuando termina un sync.

curl -X POST https://api.fiscafacil.mx/webhooks \
  -H "Authorization: Bearer ff_your_api_key_here" \
  -H "x-empresa-id: {empresaId}" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://mi-pos.com/webhooks/fiscafacil",
    "eventos": [
      "cfdi.timbrado",
      "cfdi.cancelado",
      "cfdi.sync_completado",
      "cfdi.sync_error"
    ]
  }'

Los webhooks incluyen firma HMAC-SHA256 en el header X-FiscaFacil-Signature.

Rate Limiting

Cada API key tiene su propio rate limit (configurable al crearla, default: 60 req/min). Los headers de respuesta indican el estado:

X-RateLimit-Limit: 60          # Limite configurado
X-RateLimit-Remaining: 45     # Requests restantes en la ventana
X-RateLimit-Reset: 1712764800 # Epoch cuando se reinicia la ventana

# Si excedes el limite:
HTTP 429 Too Many Requests
Retry-After: 35                # Segundos hasta que puedes reintentar

CORS (Cross-Origin)

La API permite requests cross-origin desde cualquier dominio cuando usas autenticacion por API key. Esto permite que tu POS frontend (si es web) llame directamente a la API. Los headers CORS se incluyen automaticamente en todas las respuestas de /api/*.

Seguridad

API Keys con scoping

Crea keys con el minimo de permisos necesarios. Un POS tipicamente solo necesita:cfdi:timbrar, cfdi:view,empresa:view, y empresa:csd.

IP Whitelist

Si tu POS tiene IPs fijas, usa ipWhitelist al crear el API key. Requests desde IPs no autorizadas seran rechazados silenciosamente.

Rotacion de keys

Puedes crear un nuevo API key usando uno existente (en el header Authorization), luego revocar el anterior. Zero downtime.

Encripcion de certificados

Los archivos CSD y FIEL se encriptan con AES-256-GCM antes de almacenarse. Las contrasenas nunca se guardan en texto plano.

Ejemplo: JavaScript / Node.js

const API_BASE = 'https://api.fiscafacil.mx';
const API_KEY = 'ff_your_api_key_here';

async function timbrarVenta(empresaId, venta) {
  const res = await fetch(`${API_BASE}/api/cfdi/timbrar`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      'x-empresa-id': empresaId,
    },
    body: JSON.stringify({
      receptor: venta.cliente,
      conceptos: venta.items.map(item => ({
        claveProdServ: item.claveSAT,
        claveUnidad: item.unidadSAT,
        descripcion: item.nombre,
        cantidad: item.cantidad,
        valorUnitario: item.precio,
      })),
      formaPago: venta.formaPago,
      metodoPago: 'PUE',
      moneda: 'MXN',
    }),
  });

  const json = await res.json();
  if (!json.success) {
    throw new Error(json.error?.message || 'Error al timbrar');
  }

  // Check rate limit headers
  const remaining = res.headers.get('X-RateLimit-Remaining');
  if (remaining && parseInt(remaining) < 10) {
    console.warn(`Rate limit bajo: ${remaining} requests restantes`);
  }

  return json.data; // { uuid, xml, pdf, ... }
}

Ejemplo: Python

import requests
import base64

API_BASE = "https://api.fiscafacil.mx"
API_KEY = "ff_your_api_key_here"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

# 1. Crear empresa
def crear_empresa(rfc, razon_social, regimen, cp):
    res = requests.post(f"{API_BASE}/api/empresas", headers=headers, json={
        "rfc": rfc,
        "razonSocial": razon_social,
        "regimenFiscal": regimen,
        "codigoPostal": cp,
    })
    return res.json()["data"]["id"]

# 2. Subir CSD
def subir_csd(empresa_id, cer_path, key_path, password):
    with open(cer_path, "rb") as f:
        cer_b64 = base64.b64encode(f.read()).decode()
    with open(key_path, "rb") as f:
        key_b64 = base64.b64encode(f.read()).decode()

    h = {**headers, "x-empresa-id": empresa_id}
    res = requests.post(
        f"{API_BASE}/api/empresas/{empresa_id}/certificados",
        headers=h,
        json={"cerBase64": cer_b64, "keyBase64": key_b64, "password": password},
    )
    return res.json()

# 3. Timbrar
def timbrar(empresa_id, receptor, conceptos, forma_pago="01"):
    h = {**headers, "x-empresa-id": empresa_id}
    res = requests.post(f"{API_BASE}/api/cfdi/timbrar", headers=h, json={
        "receptor": receptor,
        "conceptos": conceptos,
        "formaPago": forma_pago,
        "metodoPago": "PUE",
        "moneda": "MXN",
    })
    data = res.json()
    if not data["success"]:
        raise Exception(data["error"]["message"])
    return data["data"]

Eventos de webhook disponibles

EventoDescripcion
cfdi.timbradoUna factura fue timbrada exitosamente
cfdi.canceladoUna factura fue cancelada
cfdi.sync_completadoSync de facturas recibidas completo
cfdi.sync_errorError durante sync de facturas
cfdi.cancelacion_pendienteSolicitud de cancelacion enviada al receptor
cfdi.cancelacion_aceptadaCancelacion aceptada por el receptor
cfdi.cancelacion_rechazadaCancelacion rechazada por el receptor

Codigos de error relevantes para POS

CodigoHTTPDescripcion
AUTH_001401API key invalido o expirado
SCOPE_001403API key no tiene acceso a la empresa especificada
SCOPE_002403API key no tiene permiso para la accion solicitada
RATE_001429Rate limit excedido. Ver header Retry-After.
EMPRESA_001400Falta header x-empresa-id
CSD_004400RFC del certificado no coincide con la empresa
FIEL_004400RFC de la FIEL no coincide con la empresa

Formato de respuesta

Todas las respuestas siguen el mismo formato:

Exitosa

{
  "success": true,
  "data": { ... }
}

Error

{
  "success": false,
  "error": {
    "code": "CSD_004",
    "message": "RFC no coincide"
  }
}