# Timbrar CFDI 4.0

El endpoint de **timbrado de CFDI 4.0** permite a los clientes de QRwey! generar y timbrar
facturas de ingreso directamente a través del API, sin necesidad de generar un código QR previo.

Este flujo es ideal cuando el sistema del cliente controla completamente los datos de la
transacción y del receptor.

## Endpoint

```
POST /v1/qrs/invoices
```

**Host (DEV):**

```
https://api-dev.qrwey.com
```

**Host (PROD):**

```
https://api.qrwey.com
```

## Headers requeridos

```http
Authorization: Bearer {{access_token}}
Content-Type: application/json
```

## Request body

### Campos principales

| Campo | Descripción |
|  --- | --- |
| `sale_id` | Identificador de la venta, generado por el cliente |
| `series` | Serie del comprobante (opcional) |
| `date` | Fecha de emisión en formato ISO 8601 (requerido) |
| `expedition_zip_code` | Código postal del lugar de expedición (requerido) |
| `amount` | Total de la operación (requerido) |
| `subtotal` | Subtotal antes de impuestos (requerido) |
| `currency` | Moneda (ej. `MXN`) (requerido) |
| `exchange_rate` | Tasa de cambio, necesario si `currency` es diferente a `MXN` |
| `sat_payment_form` | Forma de pago SAT (ej. `04` Tarjeta de crédito) |
| `sat_payment_method` | Método de pago SAT (`PUE` o `PPD`) |
| `type_of_receipt` | Tipo de comprobante: `I` para factura de ingreso (requerido) |
| `export_code` | Código de exportación SAT (ej. `01` No aplica) (requerido) |


### Conceptos (`items`)

Cada elemento representa un concepto del CFDI:

| Campo | Descripción |
|  --- | --- |
| `sku` | Identificador interno del producto |
| `description` | Descripción del concepto (requerido) |
| `amount` | Importe del concepto (requerido) |
| `quantity` | Cantidad (requerido) |
| `discount` | Descuento aplicado |
| `sat_unit_key` | Clave de unidad SAT, ej. `LTR` (requerido) |
| `unit_price` | Precio unitario (requerido) |
| `sat_product_service_key` | Clave producto/servicio SAT (requerido) |
| `sat_tax_object_key` | Objeto de impuesto SAT, ej. `02` (requerido) |
| `taxes` | Impuestos aplicables al concepto |


### Impuestos del concepto (`taxes`)

| Campo | Descripción |
|  --- | --- |
| `type` | Tipo de impuesto: `Traslado` o `Retencion` (requerido) |
| `tax` | Clave del impuesto SAT, ej. `002` IVA (requerido) |
| `factor` | Tipo de factor: `Tasa`, `Cuota` o `Exento` (requerido) |
| `rate` | Tasa o cuota del impuesto, ej. `0.160000` (requerido) |
| `taxable_base` | Base imponible (requerido) |
| `amount` | Monto del impuesto (requerido) |


### Emisor (`issuer`)

| Campo | Descripción |
|  --- | --- |
| `rfc` | RFC del emisor (requerido) |
| `business_name` | Razón social del emisor (opcional, ver lógica de resolución) |
| `tax_regime_code` | Clave del régimen fiscal SAT (opcional, ver lógica de resolución) |
| `sat_credentials` | Credenciales del CSD en formato base64 (opcional) |


#### Lógica de resolución del emisor

El campo `rfc` es el único requerido en el objeto `issuer`. El sistema resuelve
el emisor con la siguiente lógica:

1. **Si se proporciona `business_name` y `tax_regime_code`**: el sistema auto-crea o actualiza
los registros de InvoiceData, Merchant y CSD (si `sat_credentials` también está presente).
Esto permite al cliente registrar un emisor al vuelo durante el timbrado.
2. **Si solo se proporciona `rfc`**: el sistema busca un emisor activo existente registrado
con ese RFC en la cuenta del customer.


> ⚠️ Si usas la opción 2 y no existe un emisor activo con ese RFC, la operación fallará con error 404.


#### Credenciales SAT (`sat_credentials`)

| Campo | Descripción |
|  --- | --- |
| `base64Cer` | Certificado CSD codificado en base64 |
| `base64Key` | Llave privada CSD codificada en base64 |
| `password` | Contraseña de la llave privada |


### Receptor (`recipient`)

| Campo | Descripción |
|  --- | --- |
| `rfc` | RFC del receptor (requerido) |
| `business_name` | Razón social del receptor (requerido) |
| `zip_code` | Código postal del domicilio fiscal (requerido) |
| `tax_regime_code` | Clave del régimen fiscal SAT (requerido) |
| `cfdi_use_code` | Clave de uso del CFDI SAT, ej. `G03` (requerido) |
| `email` | Correo electrónico del receptor (opcional) |


### CFDIs relacionados (`related_cfdis`) - Opcional

| Campo | Descripción |
|  --- | --- |
| `tipo_relacion` | Tipo de relación entre CFDIs, ej. `04` Sustitución |
| `cfdis` | Lista de UUIDs de los CFDIs relacionados |


### Información global (`global_info`) - Opcional

Para comprobantes globales de operaciones con público en general:

| Campo | Descripción |
|  --- | --- |
| `periodicity` | Periodicidad SAT, ej. `04` Mensual |
| `months` | Mes SAT, ej. `12` Diciembre |
| `year` | Anio a cuatro dígitos |


## Ejemplo de request (DEV)

```bash
curl -X POST "https://api-dev.qrwey.com/v1/qrs/invoices" \
  -H "Authorization: Bearer {{access_token}}" \
  -H "Content-Type: application/json" \
  -d '{
    "sale_id": "fcd68791-6a27-412f-a50f-8d47b7bc34d2",
    "series": "A",
    "date": "2026-03-19T14:30:00",
    "expedition_zip_code": "06600",
    "amount": 1160.00,
    "subtotal": 1000.00,
    "currency": "MXN",
    "exchange_rate": 1.0,
    "sat_payment_form": "04",
    "sat_payment_method": "PUE",
    "type_of_receipt": "I",
    "export_code": "01",
    "items": [
      {
        "sku": "GAS87",
        "description": "Gasolina Magna",
        "amount": 1000.00,
        "quantity": 49.210,
        "discount": 0.00,
        "sat_unit_key": "LTR",
        "unit_price": 20.321,
        "sat_product_service_key": "15101515",
        "sat_tax_object_key": "02",
        "taxes": [
          {
            "type": "Traslado",
            "tax": "002",
            "factor": "Tasa",
            "rate": "0.160000",
            "taxable_base": 1000.00,
            "amount": 160.00
          }
        ]
      }
    ],
    "issuer": {
      "rfc": "FUNK671228PH6",
      "business_name": "KARLA FUENTE NOLASCO",
      "tax_regime_code": "601"
    },
    "recipient": {
      "rfc": "EKU9003173C9",
      "business_name": "ESCUELA KEMPER URGATE",
      "zip_code": "42501",
      "tax_regime_code": "601",
      "cfdi_use_code": "G03",
      "email": "receptor@ejemplo.com"
    },
    "related_cfdis": null,
    "global_info": null
  }'
```

## Respuesta

```json
{
  "invoiceStatus": "GENERATED",
  "folioUuid": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
  "sourceTaxId": "FUNK671228PH6",
  "sourceLegalName": "KARLA FUENTE NOLASCO",
  "targetTaxId": "EKU9003173C9",
  "targetLegalName": "ESCUELA KEMPER URGATE",
  "subtotal": 1000.00,
  "iva": 160.00,
  "total": 1160.00,
  "invoiceDate": "2026-03-19T14:30:00Z",
  "invoiceError": null,
  "base64Xml": "PD94bWwgdmVyc2lvbj0iMS4wIiBlb..."
}
```

### Campos de la respuesta

| Campo | Descripción |
|  --- | --- |
| `invoiceStatus` | Estado del timbrado: `GENERATED`, `ERROR` |
| `folioUuid` | UUID fiscal asignado por el SAT |
| `sourceTaxId` | RFC del emisor |
| `sourceLegalName` | Razón social del emisor |
| `targetTaxId` | RFC del receptor |
| `targetLegalName` | Razón social del receptor |
| `subtotal` | Subtotal del CFDI |
| `iva` | IVA calculado |
| `total` | Total del CFDI |
| `invoiceDate` | Fecha de timbrado |
| `invoiceError` | Descripción del error (solo cuando `invoiceStatus` = `ERROR`, de lo contrario es `null`) |
| `base64Xml` | XML timbrado codificado en base64 (solo cuando `invoiceStatus` = `GENERATED`) |


## Errores comunes

| Código | Motivo |
|  --- | --- |
| 400 | Campos requeridos faltantes, datos del CFDI invalidos |
| 401 | Token inválido o expirado |
| 404 | Emisor no encontrado o no tiene llaves CSD configuradas |
| 409 | La venta ya fue facturada anteriormente (duplicado por `sale_id`) |
| 422 | Error de reglas de negocio del SAT (RFC inválido, régimen incorrecto, etc.) |


Consulta: [Manejo de errores](/guides/error-handling)

## Buenas prácticas

- Verifica que el emisor tenga llaves CSD configuradas antes de timbrar
- Usa `sale_id` como referencia interna para correlacionar con tu sistema
- Valida que los campos del receptor (RFC, razón social, código postal, régimen fiscal) sean correctos segun la Constancia de Situación Fiscal
- Para emisores recurrentes, registra el emisor previamente y usa solo el `rfc` en el request
- Guarda el `folioUuid` y las URLs del XML/PDF para tu registro contable


## ¿Qué sigue?

- Genera notas de crédito: [Notas de crédito](/guides/credit-notes)
- Genera complementos de pago: [Complementos de pago](/guides/payment-complements)
- Vuelve al índice: [Gestión de Clientes](/guides/customer-portal)