QAPI está en beta — la API puede cambiar sin previo aviso. Usa sandbox para pruebas.
QAPIQAPIDocs · by DTEVIA
Guías

Emitir una Factura

Guía paso a paso para emitir tu primera factura electrónica FACT con QAPI.

Emitir una Factura

Esta guía cubre el flujo completo: construir el request, manejar el 202 PROCESSING, hacer polling hasta CERTIFIED y obtener el PDF final.

QAPI usa un flujo asíncrono. El POST /v1/invoices responde 202 inmediatamente — la certificación ocurre en segundo plano. Nunca esperes el 202 para confirmar emisión.

Flujo completo

Construye el request

El body mínimo para una factura FACT en entorno live:

{
  "tipo_dte": "FACT",
  "moneda": "GTQ",
  "fecha_emision": "2026-06-08T10:00:00-06:00",
  "emisor": {
    "nit": "12345678",
    "nombre": "Mi Empresa S.A.",
    "nombre_comercial": "Mi Empresa",
    "codigo_establecimiento": 1,
    "afiliacion_iva": "GEN",
    "direccion": {
      "calle": "6a Avenida 1-36 Zona 1",
      "municipio": "Guatemala",
      "departamento": "Guatemala",
      "codigo_postal": "01001",
      "pais": "GT"
    },
    "correo": "facturacion@miempresa.com"
  },
  "receptor": {
    "id": "CF",
    "tipo_id": "NIT",
    "nombre": "Consumidor Final"
  },
  "frases": [
    { "tipo_frase": 1, "codigo_escenario": 1 }
  ],
  "items": [
    {
      "tipo": "S",
      "cantidad": 1,
      "unidad_medida": "UND",
      "descripcion": "Servicio de consultoría",
      "precio_unitario": 100.00,
      "descuento": 0,
      "impuestos": [
        {
          "nombre": "IVA",
          "codigo_unidad_gravable": 1,
          "monto_gravable": 100.00,
          "monto_impuesto": 12.00
        }
      ]
    }
  ],
  "metadata": {
    "referencia_interna": "ORD-2026-001"
  }
}

precio_unitario siempre SIN IVA

El precio_unitario es el precio antes de IVA. QAPI valida que monto_impuesto = monto_gravable × 0.12. Si envías el precio con IVA incluido, recibirás 422 VALIDATION_ERROR.

Envía el request

curl -X POST https://api.dtevia.com.gt/v1/invoices \
  -H "Authorization: Bearer qapi_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d @factura.json
const res = await fetch('https://api.dtevia.com.gt/v1/invoices', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer qapi_live_xxxxxxxxxxxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(invoicePayload),
});

const { id, status } = await res.json();
// status === "PROCESSING"
import axios from 'axios';

const { data } = await axios.post(
  'https://api.dtevia.com.gt/v1/invoices',
  invoicePayload,
  { headers: { Authorization: 'Bearer qapi_live_xxxxxxxxxxxx' } }
);
// data.status === "PROCESSING"
import httpx

res = httpx.post(
    "https://api.dtevia.com.gt/v1/invoices",
    json=invoice_payload,
    headers={"Authorization": "Bearer qapi_live_xxxxxxxxxxxx"},
)
data = res.json()
# data["status"] == "PROCESSING"

La respuesta 202 incluye el id de la factura y el estado PROCESSING:

{
  "id": "inv_V1StGXR8_Z5jdHi6",
  "status": "PROCESSING",
  "tipo_dte": "FACT",
  "gran_total": 112.00,
  "moneda": "GTQ",
  "numero_acceso": 543219876,
  "created_at": "2026-06-08T16:00:00.000Z",
  "links": { "self": "/v1/invoices/inv_V1StGXR8_Z5jdHi6" }
}

Espera la certificación

Puedes usar polling con backoff o configurar un webhook para recibir invoice.certified automáticamente.

async function waitForCertification(id: string, apiKey: string) {
  const delays = [1000, 2000, 2000, 3000, 5000, 5000, 10000];

  for (const delay of delays) {
    await new Promise(r => setTimeout(r, delay));

    const res = await fetch(`https://api.dtevia.com.gt/v1/invoices/${id}`, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    const invoice = await res.json();

    if (invoice.status === 'CERTIFIED') return invoice;
    if (invoice.status === 'FAILED') throw new Error(invoice.error_message);
  }

  throw new Error('Timeout esperando certificación');
}
async function waitForCertification(id: string, apiKey: string) {
  const delays = [1000, 2000, 2000, 3000, 5000, 5000, 10000];

  for (const delay of delays) {
    await new Promise(r => setTimeout(r, delay));
    const { data } = await axios.get(
      `https://api.dtevia.com.gt/v1/invoices/${id}`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );
    if (data.status === 'CERTIFIED') return data;
    if (data.status === 'FAILED') throw new Error(data.error_message);
  }

  throw new Error('Timeout esperando certificación');
}
import time

def wait_for_certification(invoice_id: str, api_key: str) -> dict:
    delays = [1, 2, 2, 3, 5, 5, 10]
    for delay in delays:
        time.sleep(delay)
        res = httpx.get(
            f"https://api.dtevia.com.gt/v1/invoices/{invoice_id}",
            headers={"Authorization": f"Bearer {api_key}"},
        )
        invoice = res.json()
        if invoice["status"] == "CERTIFIED":
            return invoice
        if invoice["status"] == "FAILED":
            raise Exception(invoice["error_message"])

    raise TimeoutError("Timeout esperando certificación")

Accede al PDF certificado

Cuando el estado es CERTIFIED, el campo pdf_url contiene el enlace al PDF con sello SAT:

{
  "id": "inv_V1StGXR8_Z5jdHi6",
  "status": "CERTIFIED",
  "uuid_sat": "550E8400-E29B-41D4-A716-446655440000",
  "serie": "A",
  "numero_dte": 42,
  "pdf_url": "https://print.totaldoc.io/pdf?uuid=550E8400-...",
  "certified_at": "2026-06-08T16:00:02.000Z"
}

El pdf_url es la URL de la representación gráfica provista por el certificador. Es un enlace estable — no expira. También puedes usar GET /v1/invoices/:id/pdf, que redirige (302) al mismo enlace.


Consideraciones importantes

Zona horaria

Guatemala usa UTC-6. Usa siempre -06:00 en el offset de fecha_emision:

"fecha_emision": "2026-06-08T10:00:00-06:00"

Consumidor Final (CF)

Para ventas sin NIT específico, usa id: "CF" con tipo_id: "NIT":

"receptor": { "id": "CF", "tipo_id": "NIT", "nombre": "Consumidor Final" }

Múltiples items

Puedes incluir varios items en un mismo DTE. Cada uno con sus propios impuestos:

"items": [
  {
    "tipo": "S",
    "cantidad": 2,
    "unidad_medida": "HRS",
    "descripcion": "Horas de desarrollo",
    "precio_unitario": 150.00,
    "descuento": 0,
    "impuestos": [{ "nombre": "IVA", "codigo_unidad_gravable": 1, "monto_gravable": 300.00, "monto_impuesto": 36.00 }]
  },
  {
    "tipo": "B",
    "cantidad": 1,
    "unidad_medida": "UND",
    "descripcion": "Licencia anual",
    "precio_unitario": 500.00,
    "descuento": 50.00,
    "impuestos": [{ "nombre": "IVA", "codigo_unidad_gravable": 1, "monto_gravable": 450.00, "monto_impuesto": 54.00 }]
  }
]

On this page